diff --git a/.claude.json b/.claude.json new file mode 100644 index 0000000..dca3674 --- /dev/null +++ b/.claude.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "beui": { + "type": "http", + "url": "https://mcp.beui.dev/mcp" + } + } +} diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..3e4859e --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,2 @@ +[mcp_servers.beui] +url = "https://mcp.beui.dev/mcp" diff --git a/.deepsource.toml b/.deepsource.toml index a4e77d7..42c10a0 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -1,5 +1,9 @@ version = 1 +exclude_patterns = [ + "src/components/motion/**", +] + [[analyzers]] name = "javascript" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 75bbf2d..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Bug Report -about: Report a bug to help us improve -title: "[BUG] " -labels: bug -assignees: 'hoangsvit' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '...' -3. Scroll down to '...' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -## Screenshots - -If applicable, add screenshots to help explain your problem. - -**Environment (please complete the following information):** - -- OS: [e.g., Windows, macOS, Linux] -- Browser [e.g., Chrome, Safari] -- Version [e.g., 22] - -## Additional context - -Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..5970e79 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,96 @@ +name: Bug report +description: Report a problem with Gmail Alias Toolkit +title: "[Bug]: " +labels: ["bug"] +assignees: + - hoangsvit +body: + - type: markdown + attributes: + value: | + Thanks for helping improve Gmail Alias Toolkit. Please include enough detail for us to reproduce the issue. + + - type: textarea + id: summary + attributes: + label: What happened? + description: Describe the bug and the part of the extension affected. + placeholder: The popup/settings page/history table does... + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: List the exact steps that trigger the issue. + placeholder: | + 1. Open the extension popup + 2. Select Custom Tags + 3. Enter ... + 4. See ... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + placeholder: I expected... + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behavior + placeholder: Instead... + validations: + required: true + + - type: dropdown + id: browser + attributes: + label: Browser + options: + - Chrome + - Microsoft Edge + - Brave + - Firefox + - Other + validations: + required: true + + - type: input + id: browser-version + attributes: + label: Browser version + placeholder: "Example: Chrome 126.0.6478.127" + + - type: input + id: extension-version + attributes: + label: Extension version + placeholder: "Example: v1.2.0" + + - type: dropdown + id: mode + attributes: + label: Theme + options: + - Light + - Dark + - System + - Not sure + + - type: textarea + id: screenshots + attributes: + label: Screenshots or recording + description: Drag screenshots, recordings, or console errors here if available. + + - type: textarea + id: extra + attributes: + label: Additional context + description: Include anything else that may help, such as imported settings, locale, or whether this happens after reload. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ddfef2d..e276a90 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,29 +1,7 @@ -description: Report a bug to help us improve -body: - - type: markdown - attributes: - value: | - Thank you for taking the time to report a bug. Please fill out the following details. - - type: input - id: title - attributes: - label: Bug title - description: Provide a short summary of the bug - placeholder: Bug title - validations: - required: true - - type: textarea - id: description - attributes: - label: Bug description - description: Provide a detailed description of the bug - placeholder: Describe the bug - validations: - required: true contact_links: - name: Feature requests and ideas - url: https://github.com/ePlus-DEV/google-cloud-skills-boost-helper/discussions/new?category=ideas + url: https://github.com/ePlus-DEV/gmail-alias-toolkit/discussions/new?category=ideas about: Suggest an idea for this project - name: Questions - url: https://github.com/ePlus-DEV/google-cloud-skills-boost-helper/discussions/new?category=q-a - about: Please ask and answer questions here \ No newline at end of file + url: https://github.com/ePlus-DEV/gmail-alias-toolkit/discussions/new?category=q-a + about: Please ask and answer questions here diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..0881265 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,56 @@ +name: Feature request +description: Suggest an improvement for Gmail Alias Toolkit +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for sharing an idea. Please describe the workflow you want to improve. + + - type: textarea + id: problem + attributes: + label: Problem or workflow + description: What are you trying to do, and what feels slow, confusing, or missing? + placeholder: When generating aliases, I want to... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed solution + description: Describe the behavior or UI you would like to see. + placeholder: Add a setting/button/filter that... + validations: + required: true + + - type: dropdown + id: area + attributes: + label: Area + options: + - Popup alias generator + - Settings + - Account management + - Alias history / favorites / statistics + - Gmail tricks + - Context menu + - Translations / i18n + - Build / CI / release + - Other + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Optional. Mention any workaround or alternate design you considered. + + - type: textarea + id: screenshots + attributes: + label: Mockups or screenshots + description: Optional. Add screenshots, sketches, or examples from similar tools. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 110c9be..9e7704d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,27 +1,44 @@ -# Description +# Summary - + - +Fixes # -## What's new? +## Type of Change - +- [ ] Bug fix +- [ ] New feature +- [ ] UI / UX update +- [ ] Translation / i18n update +- [ ] Refactor +- [ ] Tests +- [ ] Build / CI / release +- [ ] Documentation -## PR Type +## Areas Touched -What kind of change does this PR introduce? +- [ ] Popup alias generator +- [ ] Settings +- [ ] Account management +- [ ] Alias history / favorites / statistics +- [ ] Gmail tricks +- [ ] Context menu / background logic +- [ ] Browser extension manifest / permissions +- [ ] Other: -- [ ] Bugfix -- [ ] Feature -- [ ] Code style update (formatting, local variables) -- [ ] Refactoring (no functional changes, no api changes) -- [ ] Build related changes -- [ ] CI related changes -- [ ] Documentation content changes -- [ ] Tests -- [ ] Other +## Testing + +- [ ] `yarn compile` +- [ ] `yarn test` +- [ ] `yarn build` +- [ ] Loaded the extension locally and tested the changed flow +- [ ] Tested light and dark mode, if UI changed +- [ ] Checked translations, if user-facing text changed + +## Screenshots or Recording + + -## Screenshots +## Notes for Reviewers - \ No newline at end of file + diff --git a/.github/workflows/pr-auto-assign.yml b/.github/workflows/pr-auto-assign.yml index 7c00b04..68051d8 100644 --- a/.github/workflows/pr-auto-assign.yml +++ b/.github/workflows/pr-auto-assign.yml @@ -4,6 +4,7 @@ on: types: [opened] permissions: + issues: write pull-requests: write jobs: @@ -17,6 +18,83 @@ jobs: env: GH_TOKEN: ${{secrets.EPLUS_BOT_TOKEN}} + - name: Upsert welcome comment + run: | + COMMENT_ID="$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \ + --paginate \ + --jq ".[] | select(.user.login == \"eplus-bot\" and (.body | contains(\"$COMMENT_MARKER\"))) | .id" \ + | tail -n 1)" + + if [ -n "$COMMENT_ID" ]; then + gh api \ + --method PATCH \ + "repos/$GITHUB_REPOSITORY/issues/comments/$COMMENT_ID" \ + -f body="$WELCOME_COMMENT" + else + gh api \ + --method POST \ + "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \ + -f body="$WELCOME_COMMENT" + fi + env: + PR_NUMBER: ${{github.event.pull_request.number}} + COMMENT_MARKER: "" + WELCOME_COMMENT: | + + + Thank you for creating this pull request and helping make the project better. + + We will review / merge it when we are online. + GH_TOKEN: ${{secrets.EPLUS_BOT_TOKEN}} + + - name: Add pull request labels + run: | + add_label() { + local name="$1" + local color="$2" + local description="$3" + + gh api \ + --method POST \ + "repos/$GITHUB_REPOSITORY/labels" \ + -f name="$name" \ + -f color="$color" \ + -f description="$description" >/dev/null 2>&1 || true + + gh pr edit "$PR_NUMBER" --add-label "$name" --repo "$GITHUB_REPOSITORY" + } + + add_label "needs-review" "fbca04" "Pull request is ready for maintainer review" + + CHANGED_FILES="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename')" + + if echo "$CHANGED_FILES" | grep -Eq '^(entrypoints|src)/'; then + add_label "app" "1d76db" "Application or extension source code" + fi + + if echo "$CHANGED_FILES" | grep -Eq '^(entrypoints/popup|src/components|src/styles|assets)/'; then + add_label "ui" "c5def5" "User interface or visual changes" + fi + + if echo "$CHANGED_FILES" | grep -Eq '^(_locales|src/lib/i18n|.*messages\.json)'; then + add_label "i18n" "bfdadc" "Translation or localization changes" + fi + + if echo "$CHANGED_FILES" | grep -Eq '(\.test\.|\.spec\.|^tests?/|^vitest\.|testing-library)'; then + add_label "tests" "0e8a16" "Test coverage or test tooling changes" + fi + + if echo "$CHANGED_FILES" | grep -Eq '^(\.github/|package\.json|yarn\.lock|wxt\.config\.ts|tsconfig|vite\.config|postcss\.config|components\.json)'; then + add_label "build-ci" "5319e7" "Build, CI, release, or project configuration" + fi + + if echo "$CHANGED_FILES" | grep -Eq '(^README|^CHANGELOG|^docs/|\.md$)'; then + add_label "documentation" "0075ca" "Documentation changes" + fi + env: + PR_NUMBER: ${{github.event.pull_request.number}} + GH_TOKEN: ${{secrets.EPLUS_BOT_TOKEN}} + - name: Assign PR author run: gh pr edit "$PR_NUMBER" --add-assignee "$PR_AUTHOR" --repo "$GITHUB_REPOSITORY" env: diff --git a/.gitignore b/.gitignore index 1e15c72..38ed388 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* +.yarn/* +.yarn.lock node_modules .output diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..dc61281 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,6 @@ +approvedGitRepositories: + - "**" + +enableScripts: true + +nodeLinker: node-modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ebef57..abc09d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.0] - 2026-07-03 + +### Added + +- Added Tailwind CSS v4, shadcn, and beUI motion components +- Added beUI Action Swap, Animated Badge, Bouncy Accordion, Theme Toggle, Tooltip, and Table integrations +- Added dark mode toggle in the popup header +- Added locale key coverage tests to keep all translations aligned + +### Changed + +- Redesigned popup, settings, generator tabs, Gmail tricks, history table, and changelog UI with a unified beUI style +- Reworked Recent Aliases into a compact non-scrolling table with fixed action buttons and copy-on-email-click behavior +- Improved dark mode contrast, spacing, hover states, tooltips, and responsive popup layout +- Moved theme switching out of Settings and into the main popup header for faster access +- Updated all locales with the new UI strings for English, Vietnamese, French, German, Hindi, Japanese, and Simplified Chinese + +### Fixed + +- "Copy All" no longer undercounts statistics for generated aliases +- Settings/QR modals no longer render outside the popup bounds +- Tab key now moves focus normally instead of being hijacked for @gmail.com autocomplete +- Fixed missing imports and old component references after replacing legacy UI components +- Fixed table overflow and hidden row action buttons in the alias history +- Fixed untranslated/fallback strings in the new UI and added tests for locale key parity + ## [1.1.0] - 2025-12-30 ### Added + - Initial release features - Gmail alias generation with plus addressing - Preset management @@ -15,12 +42,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Statistics tracking ### Changed + - Updated dependencies ### Fixed + - Bug fixes and improvements ## [1.0.0] - 2025-12-30 ### Added + - Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3bcef89 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,148 @@ +# Contributing + +Thanks for helping improve Gmail Alias Toolkit. This guide keeps development +details out of the README while documenting the commands and project layout. + +## Requirements + +- Node.js 24 or newer +- Yarn 4.14.1 + +## Tech Stack + +- WXT and Manifest V3 +- React 19 +- TypeScript +- Tailwind CSS v4 +- shadcn and beUI motion components +- Vitest and Testing Library + +## Setup + +Install dependencies: + +```bash +yarn install +``` + +Prepare WXT types: + +```bash +yarn exec wxt prepare +``` + +## Development + +Run the Chrome development build: + +```bash +yarn dev +``` + +Run the Firefox development build: + +```bash +yarn dev:firefox +``` + +## Build + +Build for Chrome: + +```bash +yarn build +``` + +Build for Firefox: + +```bash +yarn build:firefox +``` + +Create distributable archives: + +```bash +yarn zip +yarn zip:firefox +``` + +## Quality Checks + +Run TypeScript checks: + +```bash +yarn compile +``` + +Run tests: + +```bash +yarn test +``` + +Run tests in watch mode: + +```bash +yarn test:watch +``` + +## Project Structure + +```text +gmail-alias-toolkit/ + entrypoints/ + background.ts + content.ts + popup/ + App.tsx + main.tsx + components/ + utils.ts + src/ + components/ + alias/ + motion/ + lib/ + public/ + _locales/ + tests/ + setup.ts + lib/ + popup/ + wxt.config.ts + vitest.config.ts + tsconfig.json +``` + +## Tests + +All test files live in `tests/`: + +- `tests/lib` covers shared library behavior such as i18n. +- `tests/popup` covers popup utilities and UI components. +- `tests/setup.ts` configures Testing Library, `browser`, and clipboard mocks. + +## Internationalization + +The extension uses browser i18n messages from `public/_locales`. +When adding or changing user-facing text, update every locale. The test suite +checks that every non-English locale has the same message keys as English. + +## GitHub Automation + +The repository includes GitHub Actions for: + +- CI builds on pull requests and pushes to `main`. +- PR welcome comments and automatic labels. +- PR assignment and bot review requests. +- CI-based approval comments. +- Release and Dependabot workflows. + +## Pull Requests + +1. Fork the repository. +2. Create a feature branch. +3. Keep changes scoped to the requested behavior. +4. Run `yarn compile` and `yarn test`. +5. Add screenshots or recordings for UI changes. +6. Open a pull request using the provided template. diff --git a/README.md b/README.md index 343b870..90ec139 100644 --- a/README.md +++ b/README.md @@ -1,297 +1,71 @@ -# 📧 Gmail Alias Toolkit +# Gmail Alias Toolkit -A powerful browser extension for generating and managing Gmail aliases using plus addressing (+tag). Streamline your email workflow with random generators, custom presets, favorites, and comprehensive statistics. +A lightweight browser extension that generates, manages, and organizes Gmail aliases directly from your browser. Create plus-addressed aliases (`name+shopping@gmail.com`), Gmail dot-trick variations, custom presets, and maintain a searchable history—all without sending your data anywhere. -## ✨ Features +## Features -### 🎯 Core Features -- **Random Alias Generator**: Generate secure random aliases with multiple format options - - Private Mail format (e.g., `private-mail-q2ga`) - - Alphanumeric (e.g., `abc123xy`) - - Random Words (e.g., `happy-fox-42`) - - Timestamp-based (e.g., `lk9x2m3n`) -- **Custom Tags**: Create aliases with your own custom tags -- **Gmail Tricks**: Quick access to Gmail filtering and management tricks -- **Built-in Presets**: Shopping, Work, Test, Social, Finance, Travel -- **Custom Presets**: Create and manage unlimited preset tags with context menu integration -- **Favorites System**: Star frequently used aliases for instant access -- **Recent Aliases**: View, search, filter, and manage alias history with pagination -- **Multi-Account Support**: Switch between multiple Gmail accounts seamlessly -- **Statistics Dashboard**: Track usage with detailed analytics and insights -- **Badge Counter**: Display alias count on extension icon (Total, Today, Week, or All-Time) +### Alias Generation -### 🎨 UI/UX Features -- **Modern Design**: Clean, gradient-based interface with card layouts -- **Tabbed Interface**: Organized main view with Random, Custom, and Gmail Tricks tabs -- **Pagination**: Navigate through large alias histories (5-50 items per page) -- **Search & Filter**: Real-time search and tag-based filtering -- **View Modes**: Switch between Recent and Favorites views -- **Responsive**: Optimized for 360px extension popup -- **Context Menu**: Right-click integration for quick alias generation on any editable field +- Random alias generator supporting multiple formats: private-mail, alphanumeric, words, and timestamp-based. +- Custom tag generator with user-defined presets for organized alias creation. +- Gmail tricks: dot variations, plus tags, googlemail aliases, and smart combinations. -### ⚙️ Settings & Customization +### Alias Management -#### General Settings -- **Badge Counter**: Choose what to display on extension icon - - None (Hidden) - - Total in History - - Total Generated (All Time) - - Created Today - - This Week -- **Random Format**: Select default format for random alias generation -- **Auto-save Limit**: Set history size (20-500 aliases) -- **Theme**: Light mode (Dark mode coming in next update) -- **Show Notifications**: Toggle copy confirmation messages +- Recent aliases table with full-featured search, tag filtering, and sorting. +- Favorites marking and QR code generation for quick sharing. +- Bulk export in CSV and JSON formats for backup and analysis. -#### Account Management -- **Multi-Account**: Add, edit, delete, and switch between Gmail accounts -- **Account Labels**: Organize accounts with custom labels (Work, Personal, etc.) -- **Data Isolation**: Each account has separate history, stats, and favorites -- **Quick Add**: Add accounts directly from Settings with auto-complete -- **Email Migration**: Change account email while preserving all data +### Multi-Account Support -#### Custom Presets -- Add unlimited custom preset tags -- Synced with context menu automatically -- Dynamic creation and deletion -- Display with colored badges +- Isolated history, statistics, and favorites per account. +- Seamless account switching without data leakage. -#### Data Management -- **Export Settings**: Download all settings as JSON backup -- **Import Settings**: Restore settings from backup file -- **Clear History**: Remove all recent aliases for active account -- **Reset Settings**: Restore all defaults with confirmation +### Customization & Accessibility -## 🛠 Tech Stack +- Configurable settings: badge counter display, random format selection, auto-save limits, and notifications. +- Preset management for frequently used alias patterns. +- Light and dark mode theme toggle. +- Full data import/export for migration and backup. -- **WXT**: Modern web extension framework -- **React 18**: UI library -- **TypeScript**: Type safety -- **Tailwind CSS**: Styling (CSP-safe, no CDN) -- **Manifest V3**: Latest Chrome extension standard +### Internationalization -## 📦 Installation +- Complete localization with automatic UI translation across all interfaces. -### Development -```bash -# Install dependencies -pnpm install -# or -yarn install +## Privacy -# Start development server -pnpm dev -# or -yarn dev -``` +- Data is stored locally in the browser extension storage. +- No analytics or tracking. +- No remote API is required for alias generation. +- Permissions are limited to storage, clipboard writes, context menus, and page access required by the extension context menu. -### Build for Production -```bash -# Build extension -pnpm build -# or -yarn build +## Load Locally -# Create distributable zip -pnpm zip -# or -yarn zip -``` +1. Build or run the extension locally. +2. Open `chrome://extensions/`. +3. Enable Developer mode. +4. Click Load unpacked. +5. Select `.output/chrome-mv3`. -### Load in Chrome -1. Run `pnpm dev` or `yarn dev` -2. Open Chrome and go to `chrome://extensions/` -3. Enable "Developer mode" -4. Click "Load unpacked" -5. Select the `.output/chrome-mv3` folder +For development and build commands, see `CONTRIBUTING.md`. -## 📖 How to Use +## Contributing -### Generate an Alias +Please read `CONTRIBUTING.md` for local setup, checks, project structure, and +pull request expectations. -1. **Set Your Base Email** - - Enter your Gmail address (e.g., `yourname@gmail.com`) - - The extension validates it's a Gmail address +## License -2. **Use Quick Presets** - - Click any preset button (Shopping, Work, etc.) - - Alias is automatically generated and copied +MIT. See `LICENSE.md`. -3. **Create Custom Alias** - - Type a custom tag in the input field - - Click "Generate" or press Enter - - Alias is copied to clipboard +## Support -### Manage Custom Presets +- Issues: [github.com/ePlus-DEV/gmail-alias-toolkit/issues](https://github.com/ePlus-DEV/gmail-alias-toolkit/issues) +- Discussions: [github.com/ePlus-DEV/gmail-alias-toolkit/discussions](https://github.com/ePlus-DEV/gmail-alias-toolkit/discussions) +- Email: dev@eplus.dev -1. Click the ⚙️ Settings icon -2. Go to "Presets" tab -3. Enter preset label and tag -4. Click "Add Preset" -5. Your preset appears in the main view (purple style) +## Version -### Add to Favorites +Current version: `1.2.0` -1. In the Favorites section, click "+ Add" -2. Enter a label (e.g., "Amazon") -3. Enter the tag (e.g., "amazon") -4. Click "Add" -5. Access with one click anytime - -### View Statistics - -1. Click "View Statistics" in the main popup -2. See: - - Total aliases generated - - Daily and weekly counts - - Most used tag -3. Click the X to collapse - -### Export/Import Settings - -#### Export -1. Open Settings → Advanced tab -2. Click "Export Settings" -3. JSON file downloads automatically -4. Save for backup - -#### Import -1. Open Settings → Advanced tab -2. Click "Import Settings" -3. Select your JSON backup file -4. All settings restore instantly - -### Search Recent Aliases - -1. When you have 4+ recent aliases -2. Search box appears automatically -3. Type to filter aliases in real-time -4. Click any alias to copy - -## 🎨 UI/UX Design - -- **Clean & Modern**: Professional SaaS-style design -- **Card-Based Layout**: Organized information hierarchy -- **Responsive**: Works perfectly at 360px max width -- **Intuitive Icons**: Clear visual indicators -- **Color-Coded**: Different colors for different sections - - Blue: Primary actions and default presets - - Purple: Custom presets - - Yellow: Favorites - - Green: Success states - - Red: Danger zone actions - -## 🔐 Privacy & Security - -- **Local Storage Only**: All data stored locally in your browser -- **No Analytics**: No tracking or data collection -- **No External Calls**: Works completely offline -- **No Permissions Abuse**: Only requests necessary permissions: - - `storage`: Save settings and history - - `clipboardWrite`: Copy aliases to clipboard - -## � Project Structure - -``` -gmail-alias-toolkit/ -├── entrypoints/ -│ ├── background.ts # Service worker (context menu, badge) -│ ├── content.ts # Content script -│ └── popup/ -│ ├── App.tsx # Main popup component -│ ├── main.tsx # React entry point -│ └── components/ -│ ├── Button.tsx -│ ├── Favorites.tsx -│ ├── GmailTricks.tsx -│ ├── Input.tsx -│ ├── KeyboardShortcuts.tsx -│ ├── Settings.tsx -│ ├── Statistics.tsx -│ ├── Toggle.tsx -│ └── WelcomeScreen.tsx -├── public/ -│ └── icon/ # Extension icons -├── assets/ # Static assets -├── package.json -├── wxt.config.ts # WXT configuration -├── tsconfig.json # TypeScript config -└── tailwind.config.ts # Tailwind CSS config -``` - -## 🔑 Key Components - -### App.tsx -- Main popup interface -- State management for accounts, history, favorites -- Pagination and filtering logic -- Account switching functionality - -### background.ts -- Context menu creation and handling -- Badge counter updates -- Storage change listeners -- Dynamic menu synchronization with presets - -### Settings.tsx -- Comprehensive settings interface -- Account management (add/edit/delete) -- Custom preset management -- Data import/export -- Version display from manifest - -### Statistics.tsx -- Usage analytics dashboard -- Tag-based statistics (excludes 'unknown') -- Time-based counters (today, week, all-time) - -## 🤝 Contributing - -Contributions are welcome! Please follow these guidelines: - -1. Fork the repository -2. Create a feature branch: `git checkout -b feature/your-feature` -3. Commit your changes: `git commit -m 'Add some feature'` -4. Push to the branch: `git push origin feature/your-feature` -5. Submit a pull request - -## 📄 License - -MIT License - see [LICENSE.md](LICENSE.md) for details - -## 🐛 Support - -- **Issues**: [GitHub Issues](https://github.com/yourusername/gmail-alias-toolkit/issues) -- **Email**: dev@eplus.dev - -## 📝 Changelog - -### Version 1.1.0 -- ✨ Added badge counter with 5 display options (none/total/all-time/today/week) -- 🎨 Complete Settings UI redesign with categorized card sections -- ➕ Add Account form directly in Settings tab -- 📊 Added "Total Generated (All Time)" statistics option -- 📄 Pagination system with configurable items per page (5-50) -- 🔄 Merged Favorites into Recent Aliases with tabbed interface -- 🎯 Expanded auto-save limit range (20-500) -- 💬 Email text wrapping fix (break-all for full visibility) -- 📱 Version display in Settings footer from manifest -- 🐛 Fixed duplicate state declarations bug -- 🔧 Auto-sync version from package.json to manifest - -### Version 1.0.0 -- 🎉 Initial release -- 🚀 Random alias generator with multiple formats -- 📌 Built-in and custom presets -- ⭐ Favorites system -- 📊 Statistics dashboard -- 🔄 Multi-account support -- 🎨 Modern UI with Tailwind CSS - ---- - -Made with ❤️ for Gmail power users - -🛠️ Built with: WXT + React 19 + TypeScript + Tailwind CSS - -📦 Version: 1.1.0 | 📅 Last Updated: January 2025 +See `CHANGELOG.md` for release notes. diff --git a/components.json b/components.json new file mode 100644 index 0000000..02c372b --- /dev/null +++ b/components.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "base-nova", + "rsc": false, + "tsx": true, + "iconLibrary": "lucide", + "tailwind": { + "config": "tailwind.config.ts", + "css": "entrypoints/popup/style.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "src/components", + "ui": "src/components/ui", + "utils": "src/lib/utils", + "lib": "src/lib", + "hooks": "src/lib/hooks" + }, + "registries": { + "@beui": "https://beui.dev/r/{name}.json" + } +} diff --git a/entrypoints/background.ts b/entrypoints/background.ts index 74da5d5..44d2f1e 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -1,12 +1,92 @@ -export default defineBackground(() => { - console.log("Gmail Alias Toolkit background started"); +import { + getAccountStorageKey, + getLegacyAccountStorageKey, +} from "./popup/utils"; +import { t } from "../lib/i18n"; + +interface EmailAccount { + email: string; + isActive?: boolean; +} + +interface Alias { + email: string; + timestamp: number; +} + +interface AliasStats { + total: number; + tags: Record; +} + +interface Preset { + tag: string; + label: string; +} + +interface AppSettings { + customPresets?: Preset[]; + randomFormat?: "private-mail" | "alphanumeric" | "words" | "timestamp"; + maxHistory?: number; + badgeDisplay?: "none" | "total" | "all-time" | "today" | "week"; +} +export default defineBackground(() => { // Create context menu on install browser.runtime.onInstalled.addListener(async () => { + await migrateLegacyStorageKeys(); await createContextMenus(); await updateBadge(); }); + // One-time migration from the old lossy sanitizer to the new collision-resistant key format + async function migrateLegacyStorageKeys() { + const { migration_legacy_keys_done, email_accounts, base_email } = + (await browser.storage.local.get([ + "migration_legacy_keys_done", + "email_accounts", + "base_email", + ])) as { + migration_legacy_keys_done?: boolean; + email_accounts?: EmailAccount[]; + base_email?: string; + }; + if (migration_legacy_keys_done) return; + + const emails = new Set(); + if (Array.isArray(email_accounts)) { + (email_accounts as EmailAccount[]).forEach( + (acc) => acc?.email && emails.add(acc.email), + ); + } + if (base_email) emails.add(base_email); + + const suffixes = ["gmail_alias_recent", "alias_stats", "favorites"]; + const toSet: Record = {}; + const toRemove: string[] = []; + + for (const email of emails) { + for (const suffix of suffixes) { + const legacyKey = getLegacyAccountStorageKey(email, suffix); + const newKey = getAccountStorageKey(email, suffix); + if (legacyKey === newKey) continue; + + const legacyResult = await browser.storage.local.get(legacyKey); + if (legacyResult[legacyKey] === undefined) continue; + + const newResult = await browser.storage.local.get(newKey); + if (newResult[newKey] === undefined) { + toSet[newKey] = legacyResult[legacyKey]; + } + toRemove.push(legacyKey); + } + } + + if (Object.keys(toSet).length > 0) await browser.storage.local.set(toSet); + if (toRemove.length > 0) await browser.storage.local.remove(toRemove); + await browser.storage.local.set({ migration_legacy_keys_done: true }); + } + // Recreate context menus when settings change browser.storage.onChanged.addListener(async (changes) => { if (changes.app_settings) { @@ -22,7 +102,7 @@ export default defineBackground(() => { (key) => key.startsWith("gmail_alias_recent_") || key.startsWith("alias_stats_") || - key === "email_accounts" + key === "email_accounts", ); if (shouldUpdateBadge) { await updateBadge(); @@ -34,7 +114,7 @@ export default defineBackground(() => { // Parent menu browser.contextMenus.create({ id: "gmail-alias-parent", - title: "Gmail Alias Toolkit", + title: t("extensionName"), contexts: ["editable"], }); @@ -42,7 +122,7 @@ export default defineBackground(() => { browser.contextMenus.create({ id: "fill-random-email", parentId: "gmail-alias-parent", - title: "🎲 Random Email Alias", + title: t("menuRandomEmailAlias"), contexts: ["editable"], }); @@ -50,16 +130,18 @@ export default defineBackground(() => { browser.contextMenus.create({ id: "custom-tag-parent", parentId: "gmail-alias-parent", - title: "📝 Custom Tags", + title: t("menuCustomTags"), contexts: ["editable"], }); // Load custom presets from storage - const result = await browser.storage.local.get("app_settings"); - const customPresets = result.app_settings?.customPresets || []; + const result = (await browser.storage.local.get("app_settings")) as { + app_settings?: AppSettings; + }; + const customPresets: Preset[] = result.app_settings?.customPresets || []; if (customPresets.length > 0) { - customPresets.forEach((preset: any) => { + customPresets.forEach((preset) => { browser.contextMenus.create({ id: `tag-${preset.tag}`, parentId: "custom-tag-parent", @@ -72,7 +154,7 @@ export default defineBackground(() => { browser.contextMenus.create({ id: "no-presets", parentId: "custom-tag-parent", - title: "No presets - Add in Settings", + title: t("menuNoPresets"), contexts: ["editable"], enabled: false, }); @@ -82,48 +164,49 @@ export default defineBackground(() => { browser.contextMenus.create({ id: "gmail-tricks-parent", parentId: "gmail-alias-parent", - title: "✨ Gmail Tricks", + title: t("menuGmailTricks"), contexts: ["editable"], }); browser.contextMenus.create({ id: "trick-dot", parentId: "gmail-tricks-parent", - title: "Dot Variation", + title: t("menuDotVariation"), contexts: ["editable"], }); browser.contextMenus.create({ id: "trick-googlemail", parentId: "gmail-tricks-parent", - title: "Googlemail Domain", + title: t("menuGooglemailDomain"), contexts: ["editable"], }); browser.contextMenus.create({ id: "trick-nodots", parentId: "gmail-tricks-parent", - title: "Remove All Dots", + title: t("menuRemoveAllDots"), contexts: ["editable"], }); } - // Handle context menu clicks browser.contextMenus.onClicked.addListener(async (info, tab) => { if (!tab?.id) return; // Get base email from storage - const result = await browser.storage.local.get([ + const result = (await browser.storage.local.get([ "email_accounts", "base_email", "app_settings", - ]); + ])) as { + email_accounts?: EmailAccount[]; + base_email?: string; + app_settings?: AppSettings; + }; let baseEmail = "your.email@gmail.com"; if (result.email_accounts && Array.isArray(result.email_accounts)) { - const activeAccount = result.email_accounts.find( - (acc: any) => acc.isActive - ); + const activeAccount = result.email_accounts.find((acc) => acc.isActive); if (activeAccount) { baseEmail = activeAccount.email; } @@ -140,21 +223,23 @@ export default defineBackground(() => { let randomTag = ""; switch (format) { - case "private-mail": + case "private-mail": { const chars = "abcdefghijklmnopqrstuvwxyz"; randomTag = Array.from( { length: 8 }, - () => chars[Math.floor(Math.random() * chars.length)] + () => chars[Math.floor(Math.random() * chars.length)], ).join(""); break; - case "alphanumeric": + } + case "alphanumeric": { const alphanum = "abcdefghijklmnopqrstuvwxyz0123456789"; randomTag = Array.from( { length: 10 }, - () => alphanum[Math.floor(Math.random() * alphanum.length)] + () => alphanum[Math.floor(Math.random() * alphanum.length)], ).join(""); break; - case "words": + } + case "words": { const words = [ "alpha", "beta", @@ -170,20 +255,24 @@ export default defineBackground(() => { const num = Math.floor(Math.random() * 100); randomTag = `${word1}${word2}${num}`; break; + } case "timestamp": randomTag = Date.now().toString(); break; + default: + randomTag = Date.now().toString(); + break; } emailToFill = `${username}+${randomTag}@${domain}`; - } else if (info.menuItemId?.startsWith("tag-")) { + } else if (String(info.menuItemId).startsWith("tag-")) { // Custom tag from preset - const tag = info.menuItemId.replace("tag-", ""); + const tag = String(info.menuItemId).replace("tag-", ""); emailToFill = `${username}+${tag}@${domain}`; } else if (info.menuItemId === "trick-dot") { // Dot variation - insert dot at random position const pos = Math.floor(Math.random() * (username.length - 1)) + 1; - const dottedUsername = username.slice(0, pos) + "." + username.slice(pos); + const dottedUsername = `${username.slice(0, pos)}.${username.slice(pos)}`; emailToFill = `${dottedUsername}@${domain}`; } else if (info.menuItemId === "trick-googlemail") { // Googlemail domain @@ -211,7 +300,9 @@ export default defineBackground(() => { async function updateBadge() { try { // Check badge display setting - const settingsResult = await browser.storage.local.get("app_settings"); + const settingsResult = (await browser.storage.local.get( + "app_settings", + )) as { app_settings?: AppSettings }; const badgeDisplay = settingsResult.app_settings?.badgeDisplay ?? "all-time"; @@ -221,10 +312,10 @@ export default defineBackground(() => { } // Get active account - const accountResult = await browser.storage.local.get([ + const accountResult = (await browser.storage.local.get([ "email_accounts", "base_email", - ]); + ])) as { email_accounts?: EmailAccount[]; base_email?: string }; let activeEmail = "your.email@gmail.com"; if ( @@ -232,7 +323,7 @@ export default defineBackground(() => { Array.isArray(accountResult.email_accounts) ) { const activeAccount = accountResult.email_accounts.find( - (acc: any) => acc.isActive + (acc) => acc.isActive, ); if (activeAccount) { activeEmail = activeAccount.email; @@ -244,12 +335,18 @@ export default defineBackground(() => { // Get history for active account const historyKey = getAccountStorageKey( activeEmail, - "gmail_alias_recent" + "gmail_alias_recent", ); const statsKey = getAccountStorageKey(activeEmail, "alias_stats"); - const result = await browser.storage.local.get([historyKey, statsKey]); - const recentAliases = result[historyKey] || []; - const aliasStats = result[statsKey] || { total: 0, tags: {} }; + const result = (await browser.storage.local.get([ + historyKey, + statsKey, + ])) as Record; + const recentAliases = (result[historyKey] as Alias[]) || []; + const aliasStats = (result[statsKey] as AliasStats) || { + total: 0, + tags: {}, + }; let count = 0; const now = new Date(); @@ -261,21 +358,23 @@ export default defineBackground(() => { case "all-time": count = aliasStats.total || 0; break; - case "today": + case "today": { const today = new Date( now.getFullYear(), now.getMonth(), - now.getDate() + now.getDate(), ).getTime(); - count = recentAliases.filter((a: any) => a.timestamp >= today).length; + count = recentAliases.filter((a) => a.timestamp >= today).length; break; - case "week": + } + case "week": { const weekAgo = new Date( - now.getTime() - 7 * 24 * 60 * 60 * 1000 + now.getTime() - 7 * 24 * 60 * 60 * 1000, ).getTime(); - count = recentAliases.filter( - (a: any) => a.timestamp >= weekAgo - ).length; + count = recentAliases.filter((a) => a.timestamp >= weekAgo).length; + break; + } + default: break; } @@ -292,19 +391,13 @@ export default defineBackground(() => { } } - // Helper function to get account-specific storage key - function getAccountStorageKey(email: string, suffix: string): string { - const sanitized = email.replace(/[^a-zA-Z0-9]/g, "_"); - return `${suffix}_${sanitized}`; - } - // Helper function to save email to history and stats async function saveToHistory(email: string, maxRecent: number) { // Get active account - const accountResult = await browser.storage.local.get([ + const accountResult = (await browser.storage.local.get([ "email_accounts", "base_email", - ]); + ])) as { email_accounts?: EmailAccount[]; base_email?: string }; let activeEmail = "your.email@gmail.com"; if ( @@ -312,7 +405,7 @@ export default defineBackground(() => { Array.isArray(accountResult.email_accounts) ) { const activeAccount = accountResult.email_accounts.find( - (acc: any) => acc.isActive + (acc) => acc.isActive, ); if (activeAccount) { activeEmail = activeAccount.email; @@ -326,22 +419,28 @@ export default defineBackground(() => { const statsKey = getAccountStorageKey(activeEmail, "alias_stats"); // Get current history - const result = await browser.storage.local.get([historyKey, statsKey]); - const recentAliases = result[historyKey] || []; + const result = (await browser.storage.local.get([ + historyKey, + statsKey, + ])) as Record; + const recentAliases = (result[historyKey] as Alias[]) || []; // Add to history (remove duplicates, add to top) - const newAlias = { + const newAlias: Alias = { email, timestamp: Date.now(), }; const updated = [ newAlias, - ...recentAliases.filter((a: any) => a.email !== email), + ...recentAliases.filter((a) => a.email !== email), ].slice(0, maxRecent); // Update statistics - let stats = result[statsKey] || { total: 0, tags: {} }; + const stats: AliasStats = (result[statsKey] as AliasStats) || { + total: 0, + tags: {}, + }; stats.total = (stats.total || 0) + 1; // Extract tag from email (if it has + addressing) diff --git a/entrypoints/content.ts b/entrypoints/content.ts index 9f8704f..c6dc11b 100644 --- a/entrypoints/content.ts +++ b/entrypoints/content.ts @@ -5,7 +5,7 @@ export default defineContentScript({ browser.runtime.onMessage.addListener((message) => { if (message.action === "fillEmail" && message.email) { // Get the active element (the input field that was right-clicked) - const activeElement = document.activeElement; + const activeElement = document.activeElement as HTMLElement | null; if ( activeElement && diff --git a/entrypoints/popup/App.tsx b/entrypoints/popup/App.tsx index a123fa5..9a3e7a4 100644 --- a/entrypoints/popup/App.tsx +++ b/entrypoints/popup/App.tsx @@ -1,15 +1,40 @@ -import { useState, useEffect } from 'react'; -import './App.css'; -import Settings from './components/Settings'; -import Statistics from './components/Statistics'; -import GmailTricks from './components/GmailTricks'; -import WelcomeScreen from './components/WelcomeScreen'; +import { useState, useEffect, useRef, useCallback } from "react"; +import { AnimatedToastStack } from "src/components/motion/animated-toast-stack"; +import Button from "./components/Button"; +import PopupHeader from "../../src/components/alias/PopupHeader"; +import AccountSwitcher from "../../src/components/alias/AccountSwitcher"; +import QRCode from "qrcode"; +import "./App.css"; +import Settings from "./components/Settings"; +import Statistics from "./components/Statistics"; +import WelcomeScreen from "./components/WelcomeScreen"; +import GeneratorTabs from "./components/GeneratorTabs"; +import HistorySection from "./components/HistorySection"; +import { + getAccountStorageKey, + generateAlias, + filterAliases, + type RandomFormat, +} from "./utils"; +import { t } from "../../lib/i18n"; interface Alias { email: string; timestamp: number; } +interface EmailAccount { + id: string; + email: string; + label?: string; + isActive: boolean; +} + +interface Favorite { + email: string; + timestamp?: number; +} + interface Preset { id: string; label: string; @@ -21,166 +46,272 @@ interface AppSettings { maxHistory: number; tags?: Record; total?: number; - randomFormat?: 'private-mail' | 'alphanumeric' | 'words' | 'timestamp'; + randomFormat?: "private-mail" | "alphanumeric" | "words" | "timestamp"; + theme?: "light" | "dark" | "auto"; + showNotifications?: boolean; } interface StorageResult { - [key: string]: any; gmail_alias_recent?: Alias[]; base_email?: string; app_settings?: AppSettings; + email_accounts?: EmailAccount[]; + favorites?: Favorite[]; alias_stats?: { total: number; tags: Record; }; } -const STORAGE_KEY = 'gmail_alias_recent'; - -// Helper to get account-specific storage key -const getAccountStorageKey = (email: string, suffix: string) => { - const sanitized = email.replace(/[^a-zA-Z0-9]/g, '_'); - return `${suffix}_${sanitized}`; -}; - +/** Popup root: alias generators, history, favorites, accounts, and settings. */ function App() { - const [baseEmail, setBaseEmail] = useState('your.email@gmail.com'); - const [customTag, setCustomTag] = useState(''); + /** Focuses an input once when it mounts (replaces autoFocus). */ + const focusOnMount = useCallback((el: HTMLInputElement | null) => { + el?.focus(); + }, []); + + const [baseEmail, setBaseEmail] = useState("your.email@gmail.com"); + const [customTag, setCustomTag] = useState(""); const [recentAliases, setRecentAliases] = useState([]); - const [copiedEmail, setCopiedEmail] = useState(null); + const [toastMessage, setToastMessage] = useState(null); + const [showNotifications, setShowNotifications] = useState(true); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [maxRecent, setMaxRecent] = useState(20); const [customPresets, setCustomPresets] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); - const [filterTag, setFilterTag] = useState('all'); - const [sortBy, setSortBy] = useState<'recent' | 'alphabetical'>('recent'); - const [viewMode, setViewMode] = useState<'all' | 'favorites'>('all'); + const [searchQuery, setSearchQuery] = useState(""); + const [filterTag, setFilterTag] = useState("all"); + const [sortBy, setSortBy] = useState<"recent" | "alphabetical">("recent"); + const [viewMode, setViewMode] = useState<"all" | "favorites">("all"); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); - const [randomFormat, setRandomFormat] = useState<'private-mail' | 'alphanumeric' | 'words' | 'timestamp'>('private-mail'); - const [lastGeneratedRandom, setLastGeneratedRandom] = useState(''); + const [randomFormat, setRandomFormat] = + useState("private-mail"); const [generatedRandomList, setGeneratedRandomList] = useState([]); const [randomEmailCount, setRandomEmailCount] = useState(10); - const [showRandomSettings, setShowRandomSettings] = useState(false); - const [activeGeneratorTab, setActiveGeneratorTab] = useState<'random' | 'tags' | 'tricks'>('random'); - const [emailAccounts, setEmailAccounts] = useState([]); + const [activeGeneratorTab, setActiveGeneratorTab] = useState< + "random" | "tags" | "tricks" + >("random"); + const [emailAccounts, setEmailAccounts] = useState([]); const [hasEmailAccounts, setHasEmailAccounts] = useState(true); const [showAddAccount, setShowAddAccount] = useState(false); - const [newAccountEmail, setNewAccountEmail] = useState(''); - const [newAccountLabel, setNewAccountLabel] = useState(''); - const [addAccountError, setAddAccountError] = useState(''); + const [newAccountEmail, setNewAccountEmail] = useState(""); + const [newAccountLabel, setNewAccountLabel] = useState(""); + const [addAccountError, setAddAccountError] = useState(""); const [favorites, setFavorites] = useState([]); + // Bulk delete + const [isSelectMode, setIsSelectMode] = useState(false); + const [selectedAliases, setSelectedAliases] = useState>( + new Set(), + ); + // QR code modal + const [qrAlias, setQrAlias] = useState(null); + const qrCanvasRef = useRef(null); + // Theme + const [theme, setTheme] = useState<"light" | "dark" | "auto">("light"); + + const applyTheme = useCallback((nextTheme: "light" | "dark" | "auto") => { + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + document.documentElement.classList.toggle( + "dark", + nextTheme === "dark" || (nextTheme === "auto" && prefersDark), + ); + }, []); + + const handleThemeChange = useCallback( + async (nextTheme: "light" | "dark") => { + setTheme(nextTheme); + applyTheme(nextTheme); + const result = await browser.storage.local.get("app_settings"); + await browser.storage.local.set({ + app_settings: { + ...(result.app_settings || {}), + theme: nextTheme, + }, + }); + }, + [applyTheme], + ); // Load recent aliases, base email, and settings from storage useEffect(() => { - browser.storage.local.get(['base_email', 'app_settings', 'email_accounts', 'gmail_alias_recent', 'alias_stats', 'favorites']).then(async (result: StorageResult) => { - let activeEmail = 'your.email@gmail.com'; - let needsMigration = false; - - // Load active email from email_accounts or fall back to base_email - if (result.email_accounts && Array.isArray(result.email_accounts)) { - const activeAccount = result.email_accounts.find((acc: any) => acc.isActive); - if (activeAccount) { - activeEmail = activeAccount.email; + browser.storage.local + .get([ + "base_email", + "app_settings", + "email_accounts", + "gmail_alias_recent", + "alias_stats", + "favorites", + ]) + // skipcq: JS-R1005 + .then(async (result: StorageResult) => { + let activeEmail = "your.email@gmail.com"; + let needsMigration = false; + + // Load active email from email_accounts or fall back to base_email + if (result.email_accounts && Array.isArray(result.email_accounts)) { + const activeAccount = result.email_accounts.find( + (acc) => acc.isActive, + ); + if (activeAccount) { + activeEmail = activeAccount.email; + setBaseEmail(activeEmail); + } + } else if (result.base_email) { + activeEmail = result.base_email; setBaseEmail(activeEmail); + // Check if we need to migrate from old format + needsMigration = true; } - } else if (result.base_email) { - activeEmail = result.base_email; - setBaseEmail(activeEmail); - // Check if we need to migrate from old format - needsMigration = true; - } - - // Migrate old data format to new account-specific format if needed - if (needsMigration && (result.gmail_alias_recent || result.alias_stats || result.favorites)) { - const historyKey = getAccountStorageKey(activeEmail, 'gmail_alias_recent'); - const statsKey = getAccountStorageKey(activeEmail, 'alias_stats'); - const favoritesKey = getAccountStorageKey(activeEmail, 'favorites'); - - // Only migrate if account-specific data doesn't exist yet - const accountData = await browser.storage.local.get([historyKey, statsKey, favoritesKey]); - - if (!accountData[historyKey] && !accountData[statsKey] && !accountData[favoritesKey]) { - await browser.storage.local.set({ - [historyKey]: result.gmail_alias_recent || [], - [statsKey]: result.alias_stats || { total: 0, tags: {} }, - [favoritesKey]: result.favorites || [], - }); - console.log('Migrated old data to account-specific storage for:', activeEmail); + + // Migrate old data format to new account-specific format if needed + if ( + needsMigration && + (result.gmail_alias_recent || result.alias_stats || result.favorites) + ) { + const historyKey = getAccountStorageKey( + activeEmail, + "gmail_alias_recent", + ); + const statsKey = getAccountStorageKey(activeEmail, "alias_stats"); + const favoritesKey = getAccountStorageKey(activeEmail, "favorites"); + + // Only migrate if account-specific data doesn't exist yet + const accountData = await browser.storage.local.get([ + historyKey, + statsKey, + favoritesKey, + ]); + + if ( + !accountData[historyKey] && + !accountData[statsKey] && + !accountData[favoritesKey] + ) { + await browser.storage.local.set({ + [historyKey]: result.gmail_alias_recent || [], + [statsKey]: result.alias_stats || { total: 0, tags: {} }, + [favoritesKey]: result.favorites || [], + }); + } } - } - - // Load account-specific history - const historyKey = getAccountStorageKey(activeEmail, 'gmail_alias_recent'); - const favoritesKey = getAccountStorageKey(activeEmail, 'favorites'); - const historyResult = await browser.storage.local.get([historyKey, favoritesKey]); - if (historyResult[historyKey] && Array.isArray(historyResult[historyKey])) { - setRecentAliases(historyResult[historyKey] as Alias[]); - } else { - setRecentAliases([]); - } - - // Load favorites - if (historyResult[favoritesKey] && Array.isArray(historyResult[favoritesKey])) { - const favEmails = historyResult[favoritesKey].map((f: any) => f.email); - setFavorites(favEmails); - } else { - setFavorites([]); - } - - if (result.app_settings) { - setMaxRecent(result.app_settings.maxHistory || 20); - setCustomPresets(result.app_settings.customPresets || []); - setRandomFormat(result.app_settings.randomFormat || 'private-mail'); - } - - // Load email accounts list - if (result.email_accounts && Array.isArray(result.email_accounts)) { - setEmailAccounts(result.email_accounts); - setHasEmailAccounts(result.email_accounts.length > 0); - } else if (result.base_email) { - // Legacy: has base_email but no email_accounts - setHasEmailAccounts(true); - } else { - // First time user - setHasEmailAccounts(false); - } - }); + + // Load account-specific history + const historyKey = getAccountStorageKey( + activeEmail, + "gmail_alias_recent", + ); + const favoritesKey = getAccountStorageKey(activeEmail, "favorites"); + const historyResult = await browser.storage.local.get([ + historyKey, + favoritesKey, + ]); + if ( + historyResult[historyKey] && + Array.isArray(historyResult[historyKey]) + ) { + setRecentAliases(historyResult[historyKey] as Alias[]); + } else { + setRecentAliases([]); + } + + // Load favorites + if ( + historyResult[favoritesKey] && + Array.isArray(historyResult[favoritesKey]) + ) { + const favEmails = historyResult[favoritesKey].map( + (f: Favorite) => f.email, + ); + setFavorites(favEmails); + } else { + setFavorites([]); + } + + if (result.app_settings) { + setMaxRecent(result.app_settings.maxHistory || 20); + setCustomPresets(result.app_settings.customPresets || []); + setRandomFormat(result.app_settings.randomFormat || "private-mail"); + setShowNotifications(result.app_settings.showNotifications ?? true); + const savedTheme = result.app_settings.theme || "light"; + setTheme(savedTheme); + applyTheme(savedTheme); + } + + // Load email accounts list + if (result.email_accounts && Array.isArray(result.email_accounts)) { + setEmailAccounts(result.email_accounts); + setHasEmailAccounts(result.email_accounts.length > 0); + } else if (result.base_email) { + // Legacy: has base_email but no email_accounts + setHasEmailAccounts(true); + } else { + // First time user + setHasEmailAccounts(false); + } + }); }, []); // Listen for settings changes useEffect(() => { - const handleStorageChange = async (changes: any) => { + /** Syncs settings, accounts, and favorites state when extension storage changes. */ + const handleStorageChange = async ( + changes: Record, + ) => { if (changes.app_settings) { - const newSettings = changes.app_settings.newValue; + const newSettings = changes.app_settings.newValue as + | AppSettings + | undefined; if (newSettings) { setMaxRecent(newSettings.maxHistory || 20); setCustomPresets(newSettings.customPresets || []); - setRandomFormat(newSettings.randomFormat || 'private-mail'); + setRandomFormat(newSettings.randomFormat || "private-mail"); + setShowNotifications(newSettings.showNotifications ?? true); + const newTheme = newSettings.theme || "light"; + setTheme(newTheme); + applyTheme(newTheme); } } if (changes.email_accounts) { - const newAccounts = changes.email_accounts.newValue; + const newAccounts = changes.email_accounts.newValue as + | EmailAccount[] + | undefined; if (newAccounts) { setEmailAccounts(newAccounts); setHasEmailAccounts(newAccounts.length > 0); // Update base email if active account changed - const activeAccount = newAccounts.find((acc: any) => acc.isActive); + const activeAccount = newAccounts.find((acc) => acc.isActive); if (activeAccount && activeAccount.email !== baseEmail) { setBaseEmail(activeAccount.email); // Load history for new account - const historyKey = getAccountStorageKey(activeAccount.email, 'gmail_alias_recent'); + const historyKey = getAccountStorageKey( + activeAccount.email, + "gmail_alias_recent", + ); const historyResult = await browser.storage.local.get(historyKey); - if (historyResult[historyKey] && Array.isArray(historyResult[historyKey])) { + if ( + historyResult[historyKey] && + Array.isArray(historyResult[historyKey]) + ) { setRecentAliases(historyResult[historyKey] as Alias[]); } else { setRecentAliases([]); } // Load favorites for new account - const favoritesKey = getAccountStorageKey(activeAccount.email, 'favorites'); + const favoritesKey = getAccountStorageKey( + activeAccount.email, + "favorites", + ); const favResult = await browser.storage.local.get(favoritesKey); - if (favResult[favoritesKey] && Array.isArray(favResult[favoritesKey])) { - const favEmails = favResult[favoritesKey].map((f: any) => f.email); + if ( + favResult[favoritesKey] && + Array.isArray(favResult[favoritesKey]) + ) { + const favEmails = favResult[favoritesKey].map( + (f: Favorite) => f.email, + ); setFavorites(favEmails); } else { setFavorites([]); @@ -188,13 +319,15 @@ function App() { } } } - + // Listen for favorites changes - const favoritesKey = getAccountStorageKey(baseEmail, 'favorites'); + const favoritesKey = getAccountStorageKey(baseEmail, "favorites"); if (changes[favoritesKey]) { - const newFavorites = changes[favoritesKey].newValue; + const newFavorites = changes[favoritesKey].newValue as + | Favorite[] + | undefined; if (newFavorites && Array.isArray(newFavorites)) { - const favEmails = newFavorites.map((f: any) => f.email); + const favEmails = newFavorites.map((f: Favorite) => f.email); setFavorites(favEmails); } else { setFavorites([]); @@ -211,81 +344,250 @@ function App() { setCurrentPage(1); }, [searchQuery, filterTag, viewMode, sortBy]); + // Modals are absolutely positioned against the document (popups have no stable viewport), + // so scroll to top when one opens or it can render off-screen below the fold. + useEffect(() => { + if (isSettingsOpen || qrAlias) { + window.scrollTo(0, 0); + } + }, [isSettingsOpen, qrAlias]); + // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Ctrl/Cmd + K to open settings - if ((e.ctrlKey || e.metaKey) && e.key === 'k') { + if ((e.ctrlKey || e.metaKey) && e.key === "k") { e.preventDefault(); setIsSettingsOpen(true); } // Escape to close settings - if (e.key === 'Escape' && isSettingsOpen) { + if (e.key === "Escape" && isSettingsOpen) { setIsSettingsOpen(false); } }; - globalThis.addEventListener('keydown', handleKeyDown); - return () => globalThis.removeEventListener('keydown', handleKeyDown); + globalThis.addEventListener("keydown", handleKeyDown); + return () => globalThis.removeEventListener("keydown", handleKeyDown); }, [isSettingsOpen]); - const saveRecentAlias = (email: string) => { - const newAlias: Alias = { + /** Increments the total and per-tag counters for the given generated emails. */ + const updateStats = async (emails: string[]) => { + // Use account-specific stats key + const statsKey = getAccountStorageKey(baseEmail, "alias_stats"); + const result = (await browser.storage.local.get(statsKey)) as Record< + string, + { total: number; tags: Record } | undefined + >; + const stats = result[statsKey] || { total: 0, tags: {} }; + + stats.total = (stats.total || 0) + emails.length; + stats.tags = stats.tags || {}; + + emails.forEach((email) => { + // Extract tag from email (only if it has + addressing) + const tagMatch = email.match(/\+([^@]+)@/); + if (tagMatch) { + const tag = tagMatch[1]; + stats.tags[tag] = (stats.tags[tag] || 0) + 1; + } + }); + + await browser.storage.local.set({ [statsKey]: stats }); + }; + + // Batched save: computes the merged list and stats totals once, avoiding the + // stale-closure / lost-update race that happens when saveRecentAlias is called + // N times in a tight loop (e.g. "Copy All"). + const saveRecentAliases = (emails: string[]) => { + if (emails.length === 0) return; + + const now = Date.now(); + const newAliases: Alias[] = emails.map((email, i) => ({ email, - timestamp: Date.now(), - }; + timestamp: now - i, + })); + const newEmailSet = new Set(emails); - const updated = [newAlias, ...recentAliases.filter((a) => a.email !== email)].slice( - 0, - maxRecent - ); + const updated = [ + ...newAliases, + ...recentAliases.filter((a) => !newEmailSet.has(a.email)), + ].slice(0, maxRecent); setRecentAliases(updated); - + // Save with account-specific key - const historyKey = getAccountStorageKey(baseEmail, 'gmail_alias_recent'); + const historyKey = getAccountStorageKey(baseEmail, "gmail_alias_recent"); browser.storage.local.set({ [historyKey]: updated }); // Update statistics - updateStats(email); + updateStats(emails); }; - const updateStats = async (email: string) => { - // Use account-specific stats key - const statsKey = getAccountStorageKey(baseEmail, 'alias_stats'); - const result: StorageResult = await browser.storage.local.get(statsKey); - const stats = result[statsKey] || { total: 0, tags: {} }; + /** Saves a single alias to recent history. */ + const saveRecentAlias = (email: string) => saveRecentAliases([email]); - stats.total = (stats.total || 0) + 1; + // QR code: draw when alias changes + useEffect(() => { + if (qrAlias && qrCanvasRef.current) { + (async () => { + try { + await QRCode.toCanvas(qrCanvasRef.current, qrAlias, { + width: 200, + margin: 2, + }); + } catch { + if (showNotifications) { + setToastMessage(t("toastQrFailed")); + setTimeout(() => setToastMessage(null), 2000); + } + } + })(); + } + }, [qrAlias, showNotifications]); + + /** Triggers a browser download for the given file content. */ + const downloadFile = ( + filename: string, + mimeType: string, + content: string, + ) => { + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + URL.revokeObjectURL(url); + }; - // Extract tag from email (only if it has + addressing) - const tagMatch = email.match(/\+([^@]+)@/); - if (tagMatch) { - const tag = tagMatch[1]; - stats.tags = stats.tags || {}; - stats.tags[tag] = (stats.tags[tag] || 0) + 1; + /** Exports recent aliases as a CSV or JSON download. */ + const exportAliases = (format: "csv" | "json") => { + if (recentAliases.length === 0) return; + if (format === "csv") { + const rows = recentAliases.map( + (a) => `"${a.email}","${new Date(a.timestamp).toISOString()}"`, + ); + downloadFile( + `aliases-${Date.now()}.csv`, + "text/csv", + `Email,Created At\n${rows.join("\n")}`, + ); + } else { + const data = recentAliases.map((a) => ({ + email: a.email, + createdAt: new Date(a.timestamp).toISOString(), + })); + downloadFile( + `aliases-${Date.now()}.json`, + "application/json", + JSON.stringify(data, null, 2), + ); } + if (showNotifications) { + setToastMessage(t("toastExportedAliases", String(recentAliases.length))); + setTimeout(() => setToastMessage(null), 2000); + } + }; - await browser.storage.local.set({ [statsKey]: stats }); + /** Deletes the selected aliases from history, favorites, and stats. */ + const deleteSelected = async () => { + const count = selectedAliases.size; + const updated = recentAliases.filter((a) => !selectedAliases.has(a.email)); + setRecentAliases(updated); + const historyKey = getAccountStorageKey(baseEmail, "gmail_alias_recent"); + const favoritesKey = getAccountStorageKey(baseEmail, "favorites"); + const statsKey = getAccountStorageKey(baseEmail, "alias_stats"); + + const [favResult, statsResult] = await Promise.all([ + browser.storage.local.get(favoritesKey), + browser.storage.local.get(statsKey), + ]); + + // Remove deleted emails from favorites + const currentFavs = (favResult[favoritesKey] as Favorite[]) || []; + const updatedFavs = currentFavs.filter( + (f: Favorite) => !selectedAliases.has(f.email), + ); + + // Decrement stats: total and per-tag counts + const stats = (statsResult[statsKey] as { + total: number; + tags: Record; + }) || { total: 0, tags: {} }; + const tags = { ...stats.tags }; + selectedAliases.forEach((email) => { + const match = email.match(/\+([^@]+)@/); + if (match && tags[match[1]]) { + tags[match[1]] = Math.max(0, tags[match[1]] - 1); + } + }); + // Drop tags whose count reached zero + const remainingTags = Object.fromEntries( + Object.entries(tags).filter(([, tagCount]) => tagCount > 0), + ); + const updatedStats = { + total: Math.max(0, stats.total - count), + tags: remainingTags, + }; + + await browser.storage.local.set({ + [historyKey]: updated, + [favoritesKey]: updatedFavs, + [statsKey]: updatedStats, + }); + + setFavorites(updatedFavs.map((f: Favorite) => f.email)); + setSelectedAliases(new Set()); + setIsSelectMode(false); + if (showNotifications) { + setToastMessage(t("toastDeletedAliases", String(count))); + setTimeout(() => setToastMessage(null), 2000); + } }; - const clearHistory = () => { + /** Toggles an alias in the bulk-delete selection set. */ + const toggleSelectAlias = (email: string) => { + setSelectedAliases((prev) => { + const next = new Set(prev); + if (next.has(email)) { + next.delete(email); + } else { + next.add(email); + } + return next; + }); + }; + + /** Clears all history, favorites, and stats for the active account. */ + const clearHistory = async () => { setRecentAliases([]); - const historyKey = getAccountStorageKey(baseEmail, 'gmail_alias_recent'); - browser.storage.local.set({ [historyKey]: [] }); + setFavorites([]); + const historyKey = getAccountStorageKey(baseEmail, "gmail_alias_recent"); + const favoritesKey = getAccountStorageKey(baseEmail, "favorites"); + const statsKey = getAccountStorageKey(baseEmail, "alias_stats"); + await browser.storage.local.set({ + [historyKey]: [], + [favoritesKey]: [], + [statsKey]: { total: 0, tags: {} }, + }); + if (showNotifications) { + setToastMessage(t("toastHistoryCleared")); + setTimeout(() => setToastMessage(null), 2000); + } }; + /** Adds or removes an alias from the account's favorites. */ const toggleFavorite = async (email: string) => { - const favoritesKey = getAccountStorageKey(baseEmail, 'favorites'); + const favoritesKey = getAccountStorageKey(baseEmail, "favorites"); const result = await browser.storage.local.get(favoritesKey); - const currentFavs = result[favoritesKey] || []; - - const exists = currentFavs.find((f: any) => f.email === email); - + const currentFavs = (result[favoritesKey] as Favorite[]) || []; + + const exists = currentFavs.find((f: Favorite) => f.email === email); + let updated; if (exists) { // Remove from favorites - updated = currentFavs.filter((f: any) => f.email !== email); + updated = currentFavs.filter((f: Favorite) => f.email !== email); } else { // Add to favorites const newFav = { @@ -295,871 +597,312 @@ function App() { }; updated = [...currentFavs, newFav]; } - - await browser.storage.local.set({ [favoritesKey]: updated }); - - // Update local state - const favEmails = updated.map((f: any) => f.email); - setFavorites(favEmails); - }; - const generateRandomString = (format: 'private-mail' | 'alphanumeric' | 'words' | 'timestamp', index: number = 0): string => { - if (format === 'private-mail') { - // Generate format like: private-mail-q2ga - const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; - const length = 4; - let randomStr = ''; - for (let i = 0; i < length; i++) { - randomStr += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return `private-mail-${randomStr}`; - } - - if (format === 'timestamp') { - // Add index to ensure uniqueness when generating multiple - return (Date.now() + index).toString(36); - } - - if (format === 'words') { - const adjectives = ['happy', 'sunny', 'calm', 'bright', 'swift', 'brave', 'cool', 'smart', 'quick', 'zen', 'wild', 'free', 'bold', 'wise', 'pure', 'kind', 'fair', 'true', 'rare', 'fine']; - const nouns = ['fox', 'bird', 'bear', 'wolf', 'deer', 'lion', 'hawk', 'eagle', 'tiger', 'panda', 'seal', 'otter', 'raven', 'crane', 'swan', 'lynx', 'coral', 'pearl', 'jade', 'ruby']; - const adj = adjectives[Math.floor(Math.random() * adjectives.length)]; - const noun = nouns[Math.floor(Math.random() * nouns.length)]; - const num = Math.floor(Math.random() * 999); - return `${adj}-${noun}-${num}`; - } - - // alphanumeric - const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; - const length = 8; - let result = ''; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; - }; + await browser.storage.local.set({ [favoritesKey]: updated }); - const generateRandomAlias = () => { - // Clear previous results first - setGeneratedRandomList([]); - setLastGeneratedRandom(''); - - const aliases: string[] = []; - const timestamp = Date.now(); - - for (let i = 0; i < randomEmailCount; i++) { - const randomTag = generateRandomString(randomFormat, i + timestamp); - const alias = generateAlias(randomTag); - if (alias) { - aliases.push(alias); - } + const favEmails = updated.map((f: Favorite) => f.email); + setFavorites(favEmails); + if (showNotifications) { + setToastMessage( + exists ? t("toastFavoriteRemoved") : t("toastFavoriteAdded"), + ); + setTimeout(() => setToastMessage(null), 2000); } - - // Use setTimeout to ensure state update triggers re-render - setTimeout(() => { - if (aliases.length > 0) { - setLastGeneratedRandom(aliases[0]); - setGeneratedRandomList(aliases); - // Copy first one to clipboard - copyToClipboard(aliases[0]); - } - }, 0); - }; - - const saveBaseEmail = (email: string) => { - // Only update base_email storage, don't modify account data - browser.storage.local.set({ base_email: email }); - }; - - const generateAlias = (tag: string) => { - const [username, domain] = baseEmail.split('@'); - if (!username || !domain) return null; - return `${username}+${tag}@${domain}`; }; + /** Copies an alias to the clipboard and records it in recent history. */ const copyToClipboard = async (email: string) => { try { await navigator.clipboard.writeText(email); - setCopiedEmail(email); + if (showNotifications) { + setToastMessage(t("toastCopiedEmail", email)); + } saveRecentAlias(email); - setTimeout(() => setCopiedEmail(null), 2000); - } catch (err) { - console.error('Failed to copy:', err); + setTimeout(() => { + setToastMessage(null); + }, 2000); + } catch { + if (showNotifications) { + setToastMessage(t("toastCopyFailed")); + setTimeout(() => setToastMessage(null), 2000); + } } }; + /** Generates and copies an alias for the clicked preset tag. */ const handlePresetClick = (tag: string) => { - const alias = generateAlias(tag); + const alias = generateAlias(baseEmail, tag); if (alias) { copyToClipboard(alias); } }; + /** Generates and copies an alias from the custom tag input. */ const handleCustomGenerate = () => { if (!customTag.trim()) return; - const alias = generateAlias(customTag.trim()); + const alias = generateAlias(baseEmail, customTag.trim()); if (alias) { copyToClipboard(alias); - setCustomTag(''); + setCustomTag(""); } }; + /** Generates the custom alias when Enter is pressed. */ const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { + if (e.key === "Enter") { handleCustomGenerate(); } }; + /** Validates and adds a new email account without switching to it. */ const handleAddAccount = async () => { - setAddAccountError(''); - + setAddAccountError(""); + if (!newAccountEmail.trim()) { - setAddAccountError('Email is required'); + setAddAccountError(t("errorEnterEmail")); return; } - - if (!newAccountEmail.includes('@')) { - setAddAccountError('Please enter a valid email address'); + + if (!newAccountEmail.includes("@")) { + setAddAccountError(t("errorInvalidEmail")); return; } - + // Check if email already exists - const emailExists = emailAccounts.some(acc => acc.email.toLowerCase() === newAccountEmail.trim().toLowerCase()); + const emailExists = emailAccounts.some( + (acc) => acc.email.toLowerCase() === newAccountEmail.trim().toLowerCase(), + ); if (emailExists) { - setAddAccountError('This email address is already added!'); + setAddAccountError(t("errorAccountExists")); return; } - + const newAccount = { id: Date.now().toString(), email: newAccountEmail.trim(), - label: newAccountLabel.trim() || 'Account ' + (emailAccounts.length + 1), + label: newAccountLabel.trim() || `Account ${emailAccounts.length + 1}`, isActive: false, // Don't auto-switch to new account }; - + const updatedAccounts = [...emailAccounts, newAccount]; await browser.storage.local.set({ email_accounts: updatedAccounts }); - + // Initialize empty storage for new account - const historyKey = getAccountStorageKey(newAccount.email, 'gmail_alias_recent'); - const statsKey = getAccountStorageKey(newAccount.email, 'alias_stats'); - const favoritesKey = getAccountStorageKey(newAccount.email, 'favorites'); - + const historyKey = getAccountStorageKey( + newAccount.email, + "gmail_alias_recent", + ); + const statsKey = getAccountStorageKey(newAccount.email, "alias_stats"); + const favoritesKey = getAccountStorageKey(newAccount.email, "favorites"); + await browser.storage.local.set({ [historyKey]: [], [statsKey]: { total: 0, tags: {} }, [favoritesKey]: [], }); - - setNewAccountEmail(''); - setNewAccountLabel(''); - setAddAccountError(''); + + setNewAccountEmail(""); + setNewAccountLabel(""); + setAddAccountError(""); setShowAddAccount(false); - - // Show success message briefly - const accountLabel = newAccount.label; - setCopiedEmail(`✓ ${accountLabel} added!`); - setTimeout(() => setCopiedEmail(null), 2000); + + if (showNotifications) { + setToastMessage(t("toastAccountAdded", newAccount.label)); + setTimeout(() => setToastMessage(null), 2000); + } }; + // Compute outside IIFE so bulk-delete bar can reference it + const filteredAliases = filterAliases(recentAliases, { + viewMode, + favorites, + searchQuery, + filterTag, + sortBy, + }); + + // skipcq: JS-0415 return ( -
+
{/* Show Welcome Screen for first-time users */} {!hasEmailAccounts ? ( - { - setBaseEmail(email); - setHasEmailAccounts(true); - }} - onOpenSettings={() => setIsSettingsOpen(true)} - /> +
+ { + setBaseEmail(email); + setHasEmailAccounts(true); + }} + onOpenSettings={() => setIsSettingsOpen(true)} + /> +
) : ( + // skipcq: JS-0415 <> - {/* Header */} -
-
-
-

Gmail Alias Toolkit

-

Generate aliases with plus addressing

-
- -
-
- - {/* Main Content */} -
- {/* Base Email Selector - Dropdown */} -
- -
-
- -
- - - -
-
- -
- - {/* Quick Add Account Form */} - {showAddAccount && ( -
-
- { - setNewAccountEmail(e.target.value); - setAddAccountError(''); - }} - onKeyDown={(e) => { - if (e.key === 'Tab' && newAccountEmail && !newAccountEmail.includes('@')) { - e.preventDefault(); - setNewAccountEmail(newAccountEmail + '@gmail.com'); - } - }} - placeholder="your.email" - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" - autoFocus - /> - {newAccountEmail && !newAccountEmail.includes('@') && ( -
- @gmail.com -
- )} -
- {addAccountError && ( -
-

{addAccountError}

-
- )} -

- 💡 Press Tab to add @gmail.com -

- setNewAccountLabel(e.target.value)} - placeholder="Label (optional, e.g., Work, Personal)" - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + onNewAccountEmailChange={(value) => { + setNewAccountEmail(value); + setAddAccountError(""); + }} + onNewAccountLabelChange={setNewAccountLabel} + onNewAccountBlur={() => { + if (newAccountEmail && !newAccountEmail.includes("@")) { + setNewAccountEmail(`${newAccountEmail}@gmail.com`); + } + }} + onAddAccount={handleAddAccount} + onCancelAddAccount={() => { + setShowAddAccount(false); + setNewAccountEmail(""); + setNewAccountLabel(""); + setAddAccountError(""); + }} /> -
- - -
-
- )} - - {baseEmail && !baseEmail.includes('@gmail.com') && baseEmail.includes('@') && ( -

- ⚠ This doesn't look like a Gmail address. Plus addressing works best with Gmail. -

- )} -
- - {/* Unified Email Alias Generator - RoboForm Style */} -
- {/* Header */} -
-
- - - -

Email Alias Generator

-
-
- {/* Main Tabs */} -
- - - -
+ {/* Unified Email Alias Generator */} + - {/* Tab Content */} -
- {/* Random Tab */} - {activeGeneratorTab === 'random' && ( -
- {/* Format Selector */} -
- - -
- - {/* Number of Emails */} -
- - setRandomEmailCount(Math.max(1, parseInt(e.target.value) || 10))} - className="w-20 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-purple-500" - /> -
- - {/* Generate Button */} - - - {/* Generated Emails List */} - {generatedRandomList.length > 0 && ( -
-
-
- Generated Aliases - {generatedRandomList.length} total -
-
-
- {generatedRandomList.map((email, index) => ( -
-
- {email} -
- -
- ))} -
-
- )} - -
- {randomFormat === 'private-mail' ? 'Format: private-mail-xxxx' : randomFormat === 'alphanumeric' ? '8 random characters' : randomFormat === 'words' ? '2 random words' : 'Unix timestamp'} -
-
- )} - - {/* Custom Tags Tab */} - {activeGeneratorTab === 'tags' && ( -
-
- setCustomTag(e.target.value)} - onKeyDown={handleKeyPress} - className="flex-1 px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="Enter tag (e.g., shopping, work)" - /> - -
- - {/* Custom Presets - Quick Access */} - {customPresets.length > 0 && ( -
-
Your Presets
-
- {customPresets.map((preset) => ( - - ))} -
-
- )} - -
- Example: {baseEmail.split('@')[0]}+your-tag@{baseEmail.split('@')[1]} -
-
- )} - - {/* Gmail Tricks Tab */} - {activeGeneratorTab === 'tricks' && ( -
- -
- )} -
-
+ - {/* Recent Aliases */} - {(recentAliases.length > 0 || favorites.length > 0) && ( -
-
-

- {viewMode === 'all' ? 'Recent Aliases' : 'Favorites'} -

- - {viewMode === 'all' ? `${recentAliases.length} total` : `${favorites.length} starred`} - + {/* Statistics - Collapsible */} +
- {/* View Mode Tabs */} -
-
+ + )} + + {/* QR Code Modal */} + {qrAlias && ( +
setQrAlias(null)} + > +
e.stopPropagation()} + > +

+ {t("scanToCopyAlias")} +

+ +

+ {qrAlias} +

+
+ - + -
- - {/* Search and Filters */} -
-
- setSearchQuery(e.target.value)} - placeholder="🔍 Search aliases..." - className="w-full pl-3 pr-8 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - {searchQuery && ( - - )} -
- -
- - - -
-
- -
- {(() => { - // Filter and sort aliases - const filteredAliases = recentAliases - .filter((alias) => { - // Filter by view mode - if (viewMode === 'favorites' && !favorites.includes(alias.email)) { - return false; - } - - // Filter by search query - if (searchQuery && !alias.email.toLowerCase().includes(searchQuery.toLowerCase())) { - return false; - } - - // Filter by tag - if (filterTag !== 'all') { - const tagMatch = alias.email.match(/\+([^@]+)@/); - const emailTag = tagMatch ? tagMatch[1] : null; - if (emailTag !== filterTag) { - return false; - } - } - - return true; - }) - .sort((a, b) => { - if (sortBy === 'recent') { - return b.timestamp - a.timestamp; - } else { - return a.email.localeCompare(b.email); - } - }); - - // Calculate pagination - const totalItems = filteredAliases.length; - const totalPages = Math.ceil(totalItems / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; - const paginatedAliases = filteredAliases.slice(startIndex, endIndex); - - // Empty state for favorites - if (filteredAliases.length === 0 && viewMode === 'favorites') { - return ( -
- - - -

No favorites yet

-

Star emails from your history to quick access them here

-
- ); - } - - // Render paginated list - return ( - <> - {paginatedAliases.map((alias) => ( -
- - {alias.email} - - - -
- ))} - - {/* Pagination Controls */} - {totalPages > 1 && ( -
-
- {/* Page info and items per page selector */} -
-
- Showing {startIndex + 1}-{Math.min(endIndex, totalItems)} of {totalItems} -
- -
- - {/* Page navigation */} -
- - -
- {Array.from({ length: totalPages }, (_, i) => i + 1) - .filter(page => { - // Show first, last, current, and pages around current - if (page === 1 || page === totalPages) return true; - if (Math.abs(page - currentPage) <= 1) return true; - return false; - }) - .map((page, index, array) => { - // Add ellipsis - const prevPage = array[index - 1]; - const showEllipsis = prevPage && page - prevPage > 1; - - return ( -
- {showEllipsis && ...} - -
- ); - })} -
- - -
-
-
- )} - - ); - })()} + {t("close")} +
- )} - - {/* Statistics - Collapsible */} - - - {/* Success Message */} - {copiedEmail && ( -
- ✓ Copied to clipboard! -
- )} -
- +
)} {/* Settings Modal */} diff --git a/entrypoints/popup/components/Button.tsx b/entrypoints/popup/components/Button.tsx index 5e92219..2124cd0 100644 --- a/entrypoints/popup/components/Button.tsx +++ b/entrypoints/popup/components/Button.tsx @@ -1,50 +1,2 @@ -import { ReactNode } from 'react'; - -interface ButtonProps { - children: ReactNode; - onClick?: () => void; - variant?: 'primary' | 'secondary' | 'danger' | 'success'; - size?: 'sm' | 'md' | 'lg'; - disabled?: boolean; - fullWidth?: boolean; - icon?: ReactNode; -} - -export default function Button({ - children, - onClick, - variant = 'primary', - size = 'md', - disabled = false, - fullWidth = false, - icon, -}: ButtonProps) { - const baseClasses = 'font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'; - - const variantClasses = { - primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500', - secondary: 'bg-gray-100 text-gray-700 hover:bg-gray-200 focus:ring-gray-500', - danger: 'bg-red-50 text-red-700 hover:bg-red-100 focus:ring-red-500', - success: 'bg-green-50 text-green-700 hover:bg-green-100 focus:ring-green-500', - }; - - const sizeClasses = { - sm: 'px-3 py-1.5 text-xs', - md: 'px-4 py-2 text-sm', - lg: 'px-6 py-3 text-base', - }; - - const widthClass = fullWidth ? 'w-full' : ''; - const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : ''; - - return ( - - ); -} +export { Button as default } from "../../../src/components/motion/button/base"; +export type { ButtonProps } from "../../../src/components/motion/button/base"; diff --git a/entrypoints/popup/components/Favorites.tsx b/entrypoints/popup/components/Favorites.tsx index ff8baf5..ae5ee17 100644 --- a/entrypoints/popup/components/Favorites.tsx +++ b/entrypoints/popup/components/Favorites.tsx @@ -1,4 +1,8 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react"; +import Button from "./Button"; +import { Tooltip } from "src/components/motion/tooltip"; +import { getAccountStorageKey } from "../utils"; +import { t } from "../../../lib/i18n"; interface Favorite { id: string; @@ -11,25 +15,35 @@ interface FavoritesProps { onCopy: (email: string) => void; } -// Helper to get account-specific storage key -const getAccountStorageKey = (email: string, suffix: string) => { - const sanitized = email.replace(/[^a-zA-Z0-9]/g, '_'); - return `${suffix}_${sanitized}`; -}; - +/** Lists the favorited aliases for the given base email with copy and remove actions. */ export default function Favorites({ baseEmail, onCopy }: FavoritesProps) { const [favorites, setFavorites] = useState([]); + /** Loads favorites for the current account from extension storage. */ + const loadFavorites = async () => { + const favoritesKey = getAccountStorageKey(baseEmail, "favorites"); + const result = await browser.storage.local.get(favoritesKey); + if (result[favoritesKey] && Array.isArray(result[favoritesKey])) { + setFavorites(result[favoritesKey] as Favorite[]); + } else { + setFavorites([]); + } + }; + useEffect(() => { loadFavorites(); }, [baseEmail]); useEffect(() => { // Listen for storage changes - const handleStorageChange = (changes: any) => { + const handleStorageChange = ( + changes: Record, + ) => { // Check if any favorites key changed const changedKeys = Object.keys(changes); - const relevantChange = changedKeys.some(key => key.startsWith('favorites_')); + const relevantChange = changedKeys.some((key) => + key.startsWith("favorites_"), + ); if (relevantChange || changes.email_accounts) { loadFavorites(); } @@ -39,18 +53,9 @@ export default function Favorites({ baseEmail, onCopy }: FavoritesProps) { return () => browser.storage.onChanged.removeListener(handleStorageChange); }, []); - const loadFavorites = async () => { - const favoritesKey = getAccountStorageKey(baseEmail, 'favorites'); - const result = await browser.storage.local.get(favoritesKey); - if (result[favoritesKey] && Array.isArray(result[favoritesKey])) { - setFavorites(result[favoritesKey] as Favorite[]); - } else { - setFavorites([]); - } - }; - + /** Removes an alias from favorites and persists the updated list. */ const removeFavorite = async (email: string) => { - const favoritesKey = getAccountStorageKey(baseEmail, 'favorites'); + const favoritesKey = getAccountStorageKey(baseEmail, "favorites"); const updated = favorites.filter((f) => f.email !== email); setFavorites(updated); await browser.storage.local.set({ [favoritesKey]: updated }); @@ -58,61 +63,88 @@ export default function Favorites({ baseEmail, onCopy }: FavoritesProps) { if (favorites.length === 0) { return ( -
+
-

⭐ Favorites

+

+ ⭐ Favorites +

- - + + -

No favorites yet

-

Click ⭐ on any alias in history to add it here

+

+ {t("noFavoritesYet")} +

+

+ Click ⭐ on any alias in history to add it here +

); } return ( -
+
-

⭐ Favorites

- {favorites.length} saved +

⭐ Favorites

+ + {t("favoritesSaved", String(favorites.length))} +
{favorites.map((favorite) => { const tagMatch = favorite.email.match(/\+([^@]+)@/); - const tag = tagMatch ? tagMatch[1] : 'no-tag'; - + const tag = tagMatch ? tagMatch[1] : "no-tag"; + return (
- - - + + + + +
); })} diff --git a/entrypoints/popup/components/GeneratorTabs.tsx b/entrypoints/popup/components/GeneratorTabs.tsx new file mode 100644 index 0000000..d1e7f1b --- /dev/null +++ b/entrypoints/popup/components/GeneratorTabs.tsx @@ -0,0 +1,591 @@ +// skipcq: JS-0415 - Popup tab panels are intentionally colocated for compact UI state flow. +import GmailTricks from "./GmailTricks"; +import Button from "./Button"; +import Input from "./Input"; +import { + AtSign, + Check, + Copy, + LoaderCircle, + Shuffle, + Tag, + Zap, +} from "lucide-react"; +import { useState, type ReactNode } from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "src/components/motion/select"; +import { ActionSwapButton } from "src/components/motion/action-swap"; +import { Tooltip } from "src/components/motion/tooltip"; +import { + generateAlias, + generateRandomString, + type RandomFormat, +} from "../utils"; +import { t } from "../../../lib/i18n"; + +interface Preset { + id: string; + label: string; + tag: string; +} + +interface GeneratorTabsProps { + baseEmail: string; + activeTab: "random" | "tags" | "tricks"; + setActiveTab: (tab: "random" | "tags" | "tricks") => void; + randomFormat: RandomFormat; + setRandomFormat: (format: RandomFormat) => void; + customTag: string; + setCustomTag: (tag: string) => void; + generatedRandomList: string[]; + setGeneratedRandomList: (list: string[]) => void; + randomEmailCount: number; + setRandomEmailCount: (count: number) => void; + customPresets: Preset[]; + showNotifications: boolean; + copyToClipboard: (email: string) => Promise; + handleCustomGenerate: () => void; + handleKeyPress: (e: React.KeyboardEvent) => void; + handlePresetClick: (tag: string) => void; + saveRecentAliases: (emails: string[]) => void; + setToastMessage: (msg: string | null) => void; +} + +type GeneratorTabId = "random" | "tags" | "tricks"; + +interface GeneratorTabButtonProps { + activeTab: GeneratorTabId; + tab: GeneratorTabId; + icon: ReactNode; + label: string; + tooltip: string; + onSelect: (tab: GeneratorTabId) => void; +} + +/** Compact tab button with a tooltip for truncated labels. */ +function GeneratorTabButton({ + activeTab, + tab, + icon, + label, + tooltip, + onSelect, +}: GeneratorTabButtonProps) { + const active = activeTab === tab; + + return ( + + + + ); +} + +interface GeneratorTabBarProps { + activeTab: GeneratorTabId; + onSelect: (tab: GeneratorTabId) => void; +} + +/** Top-level generator tab navigation with tooltip labels. */ +function GeneratorTabBar({ activeTab, onSelect }: GeneratorTabBarProps) { + return ( +
+ } + label={t("random")} + tooltip={t("random")} + onSelect={onSelect} + /> + } + label={t("tabTagsShort")} + tooltip={t("customTags")} + onSelect={onSelect} + /> + } + label={t("tabTricksShort")} + tooltip={t("gmailTricks")} + onSelect={onSelect} + /> +
+ ); +} + +interface RandomFormatControlsProps { + randomFormat: RandomFormat; + randomEmailCount: number; + onFormatChange: (format: RandomFormat) => void; + onCountChange: (count: number) => void; +} + +/** Format and count controls for the random alias generator. */ +function RandomFormatControls({ + randomFormat, + randomEmailCount, + onFormatChange, + onCountChange, +}: RandomFormatControlsProps) { + return ( +
+ + +
+ ); +} + +interface RandomFormatSelectProps { + randomFormat: RandomFormat; + onFormatChange: (format: RandomFormat) => void; +} + +/** Selects the random alias format. */ +function RandomFormatSelect({ + randomFormat, + onFormatChange, +}: RandomFormatSelectProps) { + return ( +
+ + +
+ ); +} + +/** Options for the random format select. */ +function RandomFormatOptions() { + return ( + + {t("privateMailFormat")} + + {t("randomCharactersFormat")} + + {t("randomWordsFormat")} + {t("timestampFormat")} + + ); +} + +interface RandomAliasCountInputProps { + randomEmailCount: number; + onCountChange: (count: number) => void; +} + +/** Numeric input for random alias batch size. */ +function RandomAliasCountInput({ + randomEmailCount, + onCountChange, +}: RandomAliasCountInputProps) { + return ( +
+ + onCountChange(Math.max(1, parseInt(value) || 10))} + className="w-full" + /> +
+ ); +} + +interface RandomAliasListProps { + generatedRandomList: string[]; + showNotifications: boolean; + copyToClipboard: (email: string) => Promise; + saveRecentAliases: (emails: string[]) => void; + setToastMessage: (msg: string | null) => void; +} + +/** Shows generated aliases with copy actions. */ +function RandomAliasList({ + generatedRandomList, + showNotifications, + copyToClipboard, + saveRecentAliases, + setToastMessage, +}: RandomAliasListProps) { + if (generatedRandomList.length === 0) return null; + + return ( +
+ +
+ {generatedRandomList.map((email) => ( + + ))} +
+
+ ); +} + +interface RandomAliasListHeaderProps { + generatedRandomList: string[]; + showNotifications: boolean; + saveRecentAliases: (emails: string[]) => void; + setToastMessage: (msg: string | null) => void; +} + +/** Header and bulk-copy action for generated aliases. */ +function RandomAliasListHeader({ + generatedRandomList, + showNotifications, + saveRecentAliases, + setToastMessage, +}: RandomAliasListHeaderProps) { + /** Copies all generated aliases and records them in recent history. */ + const copyAllAliases = async () => { + try { + await navigator.clipboard.writeText(generatedRandomList.join("\n")); + saveRecentAliases(generatedRandomList); + if (showNotifications) { + setToastMessage(t("copiedAliases", String(generatedRandomList.length))); + } + } catch { + if (showNotifications) { + setToastMessage(t("failedToCopy")); + } + } + setTimeout(() => setToastMessage(null), showNotifications ? 2000 : 0); + }; + + return ( +
+
+ + {t("generatedAliases")} + + +
+
+ ); +} + +interface RandomAliasListActionsProps { + count: number; + onCopyAll: () => void; +} + +/** Count and bulk-copy action for generated aliases. */ +function RandomAliasListActions({ + count, + onCopyAll, +}: RandomAliasListActionsProps) { + return ( +
+ + {t("totalCount", String(count))} + + + + +
+ ); +} + +interface RandomAliasRowProps { + email: string; + copyToClipboard: (email: string) => Promise; +} + +/** Single generated alias row with copy action. */ +function RandomAliasRow({ email, copyToClipboard }: RandomAliasRowProps) { + return ( +
+
+ {email} +
+ + + +
+ ); +} + +/** Renders three alias generator tabs: random (formatted strings), custom tags, and Gmail tricks. */ +export default function GeneratorTabs({ + baseEmail, + activeTab, + setActiveTab, + randomFormat, + setRandomFormat, + customTag, + setCustomTag, + generatedRandomList, + setGeneratedRandomList, + randomEmailCount, + setRandomEmailCount, + customPresets, + showNotifications, + copyToClipboard, + handleCustomGenerate, + handleKeyPress, + handlePresetClick, + saveRecentAliases, + setToastMessage, +}: GeneratorTabsProps) { + const [randomActionState, setRandomActionState] = useState< + "idle" | "generating" | "done" + >("idle"); + + /** Updates random format setting and persists to storage. */ + const handleFormatChange = async (newFormat: RandomFormat) => { + setRandomFormat(newFormat); + const result = await browser.storage.local.get("app_settings"); + const currentSettings = result.app_settings || {}; + await browser.storage.local.set({ + app_settings: { + ...currentSettings, + randomFormat: newFormat, + }, + }); + }; + + return ( +
+ {/* Main Tabs */} + + + {/* Tab Content */} +
+ {/* Random Tab */} + {activeTab === "random" && ( + // skipcq: JS-0415 - The random generator form keeps coupled controls and action state together. +
+ + + {/* Generate Button */} + { + setRandomActionState("generating"); + setGeneratedRandomList([]); + const aliases: string[] = []; + const timestamp = Date.now(); + + for (let i = 0; i < randomEmailCount; i++) { + const randomTag = generateRandomString( + randomFormat, + i + timestamp, + ); + const alias = generateAlias(baseEmail, randomTag); + if (alias) aliases.push(alias); + } + + setTimeout(() => { + if (aliases.length > 0) { + setGeneratedRandomList(aliases); + copyToClipboard(aliases[0]); + setRandomActionState("done"); + setTimeout(() => setRandomActionState("idle"), 1400); + } else { + setRandomActionState("idle"); + } + }, 0); + }} + disabled={randomActionState === "generating"} + items={[ + { + id: "idle", + icon: , + label: t("generateRandomAliases", [ + String(randomEmailCount), + randomEmailCount > 1 ? "es" : "", + ]), + }, + { + id: "generating", + icon: , + label: t("generating"), + }, + { + id: "done", + icon: , + label: t("copied"), + }, + ]} + value={randomActionState} + cycle={false} + variant="primary" + size="md" + animation="roll" + className="mb-2.5 h-10 w-full rounded-xl px-4 text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-ring" + /> + + + +
+ {randomFormat === "private-mail" + ? t("formatPrivateMail") + : randomFormat === "alphanumeric" + ? t("formatAlphanumeric") + : randomFormat === "words" + ? t("formatWords") + : t("formatTimestamp")} +
+
+ )} + + {/* Custom Tags Tab */} + {activeTab === "tags" && ( + // skipcq: JS-0415 +
+
+
+ } + /> + +
+
+ + {/* Custom Presets - Quick Access */} + {customPresets.length > 0 && ( +
+
+ {t("yourPresets")} +
+
+ {customPresets.map((preset) => ( + + ))} +
+
+ )} + +
+ + {t("example")}{" "} + + {baseEmail.split("@")[0]}+your-tag@{baseEmail.split("@")[1]} + + + + + +
+
+ )} + + {/* Gmail Tricks Tab */} + {activeTab === "tricks" && ( +
+ +
+ )} +
+
+ ); +} diff --git a/entrypoints/popup/components/GmailTricks.tsx b/entrypoints/popup/components/GmailTricks.tsx index e6887d1..b104807 100644 --- a/entrypoints/popup/components/GmailTricks.tsx +++ b/entrypoints/popup/components/GmailTricks.tsx @@ -1,176 +1,201 @@ -import { useState } from 'react'; +import { useState } from "react"; +import Button from "./Button"; +import Input from "./Input"; +import { Checkbox } from "src/components/motion/checkbox"; +import { Tooltip } from "src/components/motion/tooltip"; +import { Copy, Dices, Info, Zap } from "lucide-react"; +import { getDotVariationCandidates } from "../utils"; +import { t } from "../../../lib/i18n"; interface GmailTricksProps { baseEmail: string; onCopy: (email: string) => void; } +/** Panel that generates Gmail trick variations (dots, plus tags, googlemail, combos) for the base email. */ export default function GmailTricks({ baseEmail, onCopy }: GmailTricksProps) { - const [selectedTrick, setSelectedTrick] = useState<'dot' | 'googlemail' | 'nodots' | 'combo' | 'plus' | 'dotplus'>('dot'); + const [selectedTrick, setSelectedTrick] = useState< + "dot" | "googlemail" | "nodots" | "combo" | "plus" | "dotplus" + >("dot"); const [tricksCount, setTricksCount] = useState(10); const [generatedTricks, setGeneratedTricks] = useState([]); const [randomizeDots, setRandomizeDots] = useState(false); - const generateDotVariations = (username: string, count: number = 10): string[] => { - if (username.length < 2) return []; - - const variations: string[] = []; - - if (randomizeDots) { - // Random dot positions - truly random each time - for (let i = 0; i < count; i++) { - const chars = username.split(''); - const maxDots = Math.min(3, chars.length - 1); - const numDots = Math.floor(Math.random() * maxDots) + 1; - const positions = new Set(); - - // Generate truly random positions - while (positions.size < numDots) { - const pos = Math.floor(Math.random() * (chars.length - 1)) + 1; - positions.add(pos); - } - - // Insert dots at random positions - const sortedPositions = Array.from(positions).sort((a, b) => a - b); - let result = ''; - let lastPos = 0; - sortedPositions.forEach(pos => { - result += chars.slice(lastPos, pos).join('') + '.'; - lastPos = pos; - }); - result += chars.slice(lastPos).join(''); - - variations.push(result); - } - } else { - // Sequential dot positions (original behavior) - const len = username.length; - for (let i = 1; i < len; i++) { - variations.push(username.slice(0, i) + '.' + username.slice(i)); - } - - // Generate multiple dots - if (username.length >= 4) { - for (let i = 1; i < len - 1; i++) { - for (let j = i + 1; j < len; j++) { - variations.push( - username.slice(0, i) + '.' + - username.slice(i, j) + '.' + - username.slice(j) - ); - } - } - } - } - - return [...new Set(variations)].slice(0, count); - }; + /** Combines dot variations with common plus tags, capped at `count` results. */ + const generateCombinations = (count = 10): string[] => { + if (!baseEmail.includes("@")) return []; - const generateGooglemailVariation = (): string | null => { - if (!baseEmail.includes('@')) return null; - - const [username, domain] = baseEmail.split('@'); - if (domain === 'gmail.com') { - return `${username}@googlemail.com`; - } else if (domain === 'googlemail.com') { - return `${username}@gmail.com`; - } - return null; - }; + const [username, domain] = baseEmail.split("@"); + const normalizedDomain = domain.toLowerCase(); + const isGmail = + normalizedDomain === "gmail.com" || normalizedDomain === "googlemail.com"; + if (!isGmail) return []; - const generateCombinations = (count: number = 10): string[] => { - if (!baseEmail.includes('@')) return []; - - const [username, domain] = baseEmail.split('@'); - if (!domain.includes('gmail')) return []; - const combinations: string[] = []; - const dotVariations = generateDotVariations(username, count); - + const dotVariations = getDotVariationCandidates( + username, + count, + randomizeDots, + ); + // Dot + common tags - const commonTags = ['newsletter', 'shop', 'spam', 'work', 'personal', 'test', 'promo', 'social', 'finance', 'travel']; - dotVariations.forEach(dotUser => { - commonTags.forEach(tag => { + const commonTags = [ + "newsletter", + "shop", + "spam", + "work", + "personal", + "test", + "promo", + "social", + "finance", + "travel", + ]; + dotVariations.forEach((dotUser) => { + commonTags.forEach((tag) => { combinations.push(`${dotUser}+${tag}@${domain}`); }); }); - + return combinations.slice(0, count); }; - const generatePlusVariations = (count: number = 10): string[] => { - if (!baseEmail.includes('@')) return []; - - const [username, domain] = baseEmail.split('@'); + /** Generates plus-tag aliases from a list of common tags, capped at `count` results. */ + const generatePlusVariations = (count = 10): string[] => { + if (!baseEmail.includes("@")) return []; + + const [username, domain] = baseEmail.split("@"); const tags = [ - 'newsletter', 'shop', 'spam', 'work', 'personal', 'test', 'promo', - 'social', 'finance', 'travel', 'amazon', 'ebay', 'facebook', 'twitter', - 'linkedin', 'github', 'google', 'microsoft', 'apple', 'samsung', - 'newsletter1', 'newsletter2', 'deals', 'offers', 'alerts', 'updates', - 'notifications', 'receipts', 'invoices', 'subscriptions' + "newsletter", + "shop", + "spam", + "work", + "personal", + "test", + "promo", + "social", + "finance", + "travel", + "amazon", + "ebay", + "facebook", + "twitter", + "linkedin", + "github", + "google", + "microsoft", + "apple", + "samsung", + "newsletter1", + "newsletter2", + "deals", + "offers", + "alerts", + "updates", + "notifications", + "receipts", + "invoices", + "subscriptions", ]; - - return tags.slice(0, count).map(tag => `${username}+${tag}@${domain}`); + + return tags.slice(0, count).map((tag) => `${username}+${tag}@${domain}`); }; - const generateDotPlusVariations = (count: number = 10): string[] => { - if (!baseEmail.includes('@')) return []; - - const [username, domain] = baseEmail.split('@'); - const dotVars = generateDotVariations(username, Math.ceil(count / 3)); - const tags = ['shop', 'work', 'test', 'spam', 'newsletter', 'promo', 'social', 'finance']; + /** Combines dot variations with plus tags, capped at `count` results. */ + const generateDotPlusVariations = (count = 10): string[] => { + if (!baseEmail.includes("@")) return []; + + const [username, domain] = baseEmail.split("@"); + const dotVars = getDotVariationCandidates( + username, + Math.ceil(count / 3), + randomizeDots, + ); + const tags = [ + "shop", + "work", + "test", + "spam", + "newsletter", + "promo", + "social", + "finance", + ]; const results: string[] = []; - - dotVars.forEach(dotUser => { - tags.forEach(tag => { + + dotVars.forEach((dotUser) => { + tags.forEach((tag) => { results.push(`${dotUser}+${tag}@${domain}`); }); }); - + return results.slice(0, count); }; + /** Generates variations for the currently selected trick and copies the first result. */ const generateTricksVariations = () => { - if (!baseEmail.includes('@')) return; - + if (!baseEmail.includes("@")) return; + // Clear previous results first to force re-render setGeneratedTricks([]); - - const [username, domain] = baseEmail.split('@'); + + const [username, domain] = baseEmail.split("@"); let results: string[] = []; - + switch (selectedTrick) { - case 'dot': - results = generateDotVariations(username, tricksCount).map(u => `${u}@${domain}`); + case "dot": + results = getDotVariationCandidates( + username, + tricksCount, + randomizeDots, + ).map((u) => `${u}@${domain}`); break; - case 'googlemail': - const altDomain = domain === 'gmail.com' ? 'googlemail.com' : 'gmail.com'; - results = generateDotVariations(username, tricksCount).map(u => `${u}@${altDomain}`); + case "googlemail": { + const altDomain = + domain.toLowerCase() === "gmail.com" ? "googlemail.com" : "gmail.com"; + results = getDotVariationCandidates( + username, + tricksCount, + randomizeDots, + ).map((u) => `${u}@${altDomain}`); break; - case 'nodots': - const noDots = username.replace(/\./g, ''); + } + case "nodots": { + const noDots = username.replace(/\./g, ""); const noDotResults = [ `${noDots}@${domain}`, - `${noDots}@${domain === 'gmail.com' ? 'googlemail.com' : 'gmail.com'}`, + `${noDots}@${domain === "gmail.com" ? "googlemail.com" : "gmail.com"}`, ]; // Generate with plus tags too - const tags = ['work', 'shop', 'test', 'spam', 'newsletter', 'promo', 'social', 'finance']; - tags.forEach(tag => { + const tags = [ + "work", + "shop", + "test", + "spam", + "newsletter", + "promo", + "social", + "finance", + ]; + tags.forEach((tag) => { noDotResults.push(`${noDots}+${tag}@${domain}`); }); results = noDotResults.slice(0, tricksCount); break; - case 'plus': + } + case "plus": results = generatePlusVariations(tricksCount); break; - case 'dotplus': + case "dotplus": results = generateDotPlusVariations(tricksCount); break; - case 'combo': + case "combo": results = generateCombinations(tricksCount); break; + default: + break; } - + // Use setTimeout to ensure state update triggers re-render setTimeout(() => { setGeneratedTricks(results); @@ -180,153 +205,151 @@ export default function GmailTricks({ baseEmail, onCopy }: GmailTricksProps) { }, 0); }; - const removeDots = (): string | null => { - if (!baseEmail.includes('@')) return null; - - const [username, domain] = baseEmail.split('@'); - const noDots = username.replace(/\./g, ''); - return `${noDots}@${domain}`; - }; + /** Builds the visual state classes for each Gmail trick selector. */ + const trickButtonClass = (active: boolean) => + `h-10 min-w-0 rounded-xl border px-2 text-xs font-medium transition-colors ${ + active + ? "border-primary/35 bg-primary/10 text-foreground" + : "border-border bg-background text-muted-foreground hover:bg-muted/70 hover:text-foreground" + }`; + // skipcq: JS-0415 return ( + // skipcq: JS-0415
{/* Trick Type Selector */}
- - + - + - + - + - + + {t("allCombos")} +
{/* Options */}
- - + {t("numberOfVariations")} + + setTricksCount(Math.max(1, parseInt(e.target.value) || 10))} - className="w-20 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500" + value={String(tricksCount)} + onChange={(value) => + setTricksCount(Math.max(1, parseInt(value) || 10)) + } + className="w-20" />
- + {/* Random Dots Toggle - only show for dot-related tricks */} - {(selectedTrick === 'dot' || selectedTrick === 'googlemail' || selectedTrick === 'dotplus' || selectedTrick === 'combo') && ( -
- + setRandomizeDots(e.target.checked)} - className="w-4 h-4 text-green-600 bg-white border-gray-300 rounded focus:ring-green-500" + onCheckedChange={setRandomizeDots} /> -
)}
{/* Generate Button */} - + {/* Generated Tricks List */} {generatedTricks.length > 0 && ( -
-
+
+
- Generated Variations - {generatedTricks.length} total + + {t("generatedVariations")} + + + {t("totalCount", String(generatedTricks.length))} +
- {generatedTricks.map((email, index) => ( + {generatedTricks.map((email) => (
-
+
{email}
- + + +
))}
@@ -334,13 +357,14 @@ export default function GmailTricks({ baseEmail, onCopy }: GmailTricksProps) { )} {/* Info */} -
+
- - - -

- Gmail trick: Dots are ignored & everything after + goes to same inbox + +

+ + {t("gmailTrickInfoLabel")} + {" "} + {t("gmailTrickInfo")}

diff --git a/entrypoints/popup/components/HistorySection.tsx b/entrypoints/popup/components/HistorySection.tsx new file mode 100644 index 0000000..191a518 --- /dev/null +++ b/entrypoints/popup/components/HistorySection.tsx @@ -0,0 +1,704 @@ +/** Recent aliases list with search, filter, pagination, and bulk selection. */ +import { useMemo } from "react"; +import Button from "./Button"; +import Input from "./Input"; +import { + ChevronLeft, + ChevronRight, + ChevronsLeft, + ChevronsRight, + QrCode, + Star, +} from "lucide-react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "src/components/motion/select"; +import { Checkbox } from "src/components/motion/checkbox"; +import { Tooltip } from "src/components/motion/tooltip"; +import { Table, type TableColumn } from "src/components/motion/table"; +import { AnimatedBadge } from "src/components/motion/animated-badge"; +import { t } from "../../../lib/i18n"; + +interface Alias { + email: string; + timestamp: number; +} + +/** Shortens long email aliases while keeping the domain readable. */ +function shortenEmail(email: string) { + const [local, domain] = email.split("@"); + if (!local || !domain || email.length <= 30) return email; + const visibleLocal = + local.length > 20 ? `${local.slice(0, 14)}...${local.slice(-4)}` : local; + return `${visibleLocal}@${domain}`; +} + +interface HistorySectionProps { + recentAliases: Alias[]; + favorites: string[]; + searchQuery: string; + setSearchQuery: (q: string) => void; + filterTag: string; + setFilterTag: (tag: string) => void; + sortBy: "recent" | "alphabetical"; + setSortBy: (sort: "recent" | "alphabetical") => void; + viewMode: "all" | "favorites"; + setViewMode: (mode: "all" | "favorites") => void; + currentPage: number; + setCurrentPage: (page: number) => void; + itemsPerPage: number; + setItemsPerPage: (count: number) => void; + isSelectMode: boolean; + setIsSelectMode: (on: boolean) => void; + selectedAliases: Set; + setSelectedAliases: (set: Set) => void; + filteredAliases: Alias[]; + exportAliases: (format: "csv" | "json") => void; + deleteSelected: () => void; + toggleSelectAlias: (email: string) => void; + toggleFavorite: (email: string) => void; + copyToClipboard: (email: string) => Promise; + setQrAlias: (email: string | null) => void; +} + +/** Recent aliases list with search, filter, pagination, and bulk selection. */ +export default function HistorySection({ + recentAliases, + favorites, + searchQuery, + setSearchQuery, + filterTag, + setFilterTag, + sortBy, + setSortBy, + viewMode, + setViewMode, + currentPage, + setCurrentPage, + itemsPerPage, + setItemsPerPage, + isSelectMode, + setIsSelectMode, + selectedAliases, + setSelectedAliases, + filteredAliases, + exportAliases, + deleteSelected, + toggleSelectAlias, + toggleFavorite, + copyToClipboard, + setQrAlias, +}: HistorySectionProps) { + if (recentAliases.length === 0 && favorites.length === 0) return null; + + // skipcq: JS-0415 + return ( + // skipcq: JS-0415 +
+ {/* Header with title and action buttons */} +
+
+

+ {viewMode === "all" ? t("recentAliases") : t("favorites")} +

+
+ + {viewMode === "all" + ? t("totalCount", String(recentAliases.length)) + : t("starredCount", String(favorites.length))} + +
+
+ {viewMode === "all" && recentAliases.length > 0 && ( +
+ + + + + + + + + +
+ )} +
+ + {/* Bulk delete bar */} + {isSelectMode && ( +
+ + + {t("selectedCount", String(selectedAliases.size))} + + +
+ )} + + {/* View mode tabs */} +
+ + +
+ + {/* Search and filters */} +
+
+ + {searchQuery && ( + + )} +
+ +
+ + + +
+
+ + {/* Aliases list with pagination */} + +
+ ); +} + +/** Paginated list of aliases with copy, favorite, and QR actions. */ +function HistoryList({ + filteredAliases, + currentPage, + setCurrentPage, + itemsPerPage, + setItemsPerPage, + isSelectMode, + selectedAliases, + toggleSelectAlias, + favorites, + toggleFavorite, + copyToClipboard, + setQrAlias, + viewMode, +}: { + filteredAliases: Alias[]; + currentPage: number; + setCurrentPage: (page: number) => void; + itemsPerPage: number; + setItemsPerPage: (count: number) => void; + isSelectMode: boolean; + selectedAliases: Set; + toggleSelectAlias: (email: string) => void; + favorites: string[]; + toggleFavorite: (email: string) => void; + copyToClipboard: (email: string) => Promise; + setQrAlias: (email: string | null) => void; + viewMode: "all" | "favorites"; +}) { + const totalItems = filteredAliases.length; + const totalPages = Math.ceil(totalItems / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const paginatedAliases = filteredAliases.slice(startIndex, endIndex); + const columns = useMemo[]>( + () => [ + ...(isSelectMode + ? [ + { + key: "select", + header: "", + width: "36px", + align: "center" as const, + cell: (alias: Alias) => ( + toggleSelectAlias(alias.email)} + aria-label={alias.email} + /> + ), + }, + ] + : []), + { + key: "email", + header: t("aliasColumn"), + width: isSelectMode ? "142px" : "180px", + cell: (alias) => ( + + + + ), + sortValue: (alias) => alias.email, + }, + { + key: "actions", + header: "", + width: "58px", + align: "left", + cell: (alias) => { + const isFavorited = favorites.includes(alias.email); + + return ( +
+ + + + + + +
+ ); + }, + }, + ], + [ + copyToClipboard, + favorites, + isSelectMode, + selectedAliases, + setQrAlias, + toggleFavorite, + toggleSelectAlias, + ], + ); + + if (filteredAliases.length === 0 && viewMode === "favorites") { + return ( +
+ + + +

{t("noFavoritesYet")}

+

{t("starEmailsHint")}

+
+ ); + } + + if (filteredAliases.length === 0) { + return ( +
+ + + +

{t("noResultsFound")}

+

{t("differentSearchHint")}

+
+ ); + } + + // skipcq: JS-0415 + return ( +
+ alias.email} + rowHeight={44} + height="auto" + defaultSort={null} + emptyState={t("noResultsFound")} + className="rounded-xl bg-card shadow-sm [&>div]:overflow-x-hidden [&_td]:min-w-0 [&_td]:px-2 [&_th]:bg-muted/80 [&_th]:px-2 [&_th]:text-foreground" + /> + + {totalPages > 1 && ( + + )} + + ); +} + +/** Pagination controls. */ +function Pagination({ + currentPage, + setCurrentPage, + totalPages, + itemsPerPage, + setItemsPerPage, + startIndex, + endIndex, + totalItems, +}: { + currentPage: number; + setCurrentPage: (page: number) => void; + totalPages: number; + itemsPerPage: number; + setItemsPerPage: (count: number) => void; + startIndex: number; + endIndex: number; + totalItems: number; +}) { + // skipcq: JS-0415 + return ( + // skipcq: JS-0415 +
+
+ {/* Page info */} +
+
+ {t("showingRange", [ + String(startIndex + 1), + String(Math.min(endIndex, totalItems)), + String(totalItems), + ])} +
+ +
+ + {/* Navigation buttons */} +
+ + +
+ {Array.from({ length: totalPages }, (_, i) => i + 1) + .filter( + (page) => + page === 1 || + page === totalPages || + Math.abs(page - currentPage) <= 1, + ) + .map((page, index, array) => { + const prevPage = array[index - 1]; + const showEllipsis = prevPage && page - prevPage > 1; + + return ( +
+ {showEllipsis && ( + ... + )} + +
+ ); + })} +
+ + +
+
+
+ ); +} diff --git a/entrypoints/popup/components/Input.tsx b/entrypoints/popup/components/Input.tsx index f16b37a..a234d31 100644 --- a/entrypoints/popup/components/Input.tsx +++ b/entrypoints/popup/components/Input.tsx @@ -1,38 +1,2 @@ -interface InputProps { - type?: 'text' | 'email' | 'number'; - value: string | number; - onChange: (value: string) => void; - placeholder?: string; - label?: string; - disabled?: boolean; - onKeyPress?: (e: React.KeyboardEvent) => void; -} - -export default function Input({ - type = 'text', - value, - onChange, - placeholder, - label, - disabled = false, - onKeyPress, -}: InputProps) { - return ( -
- {label && ( - - )} - onChange(e.target.value)} - onKeyPress={onKeyPress} - placeholder={placeholder} - disabled={disabled} - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed" - /> -
- ); -} +export { Input as default } from "../../../src/components/motion/input"; +export type { InputProps } from "../../../src/components/motion/input"; diff --git a/entrypoints/popup/components/KeyboardShortcuts.tsx b/entrypoints/popup/components/KeyboardShortcuts.tsx index a2074e7..af5a9f3 100644 --- a/entrypoints/popup/components/KeyboardShortcuts.tsx +++ b/entrypoints/popup/components/KeyboardShortcuts.tsx @@ -1,47 +1,95 @@ -import { useState } from 'react'; +import { useState } from "react"; +import Button from "./Button"; +/** Button that opens a modal listing available keyboard shortcuts. */ export default function KeyboardShortcuts() { const [isOpen, setIsOpen] = useState(false); const shortcuts = [ - { key: 'Enter', description: 'Generate alias with custom tag', context: 'In custom tag input' }, - { key: 'Ctrl/Cmd + K', description: 'Open settings', context: 'Anywhere' }, - { key: 'Esc', description: 'Close settings', context: 'In settings' }, + { + key: "Enter", + description: "Generate alias with custom tag", + context: "In custom tag input", + }, + { key: "Ctrl/Cmd + K", description: "Open settings", context: "Anywhere" }, + { key: "Esc", description: "Close settings", context: "In settings" }, ]; return ( <> - + {isOpen && ( -
setIsOpen(false)}> -
e.stopPropagation()}> -
+ // skipcq: JS-0415 +
setIsOpen(false)} + > +
e.stopPropagation()} + > +
-

Keyboard Shortcuts

- +
- {shortcuts.map((shortcut, index) => ( -
+ {shortcuts.map((shortcut) => ( +
-
{shortcut.description}
-
{shortcut.context}
+
+ {shortcut.description} +
+
+ {shortcut.context} +
- + {shortcut.key}
diff --git a/entrypoints/popup/components/Settings.tsx b/entrypoints/popup/components/Settings.tsx index 40cfdce..7c144c0 100644 --- a/entrypoints/popup/components/Settings.tsx +++ b/entrypoints/popup/components/Settings.tsx @@ -1,7 +1,21 @@ -import { useState, useEffect } from 'react'; -import Toggle from './Toggle'; -import Button from './Button'; -import Input from './Input'; +// skipcq: JS-0415 - Settings sections keep related controls inline for readability in a constrained popup. +import { useState, useEffect, useCallback } from "react"; +import Toggle from "./Toggle"; +import Button from "./Button"; +import Input from "./Input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "src/components/motion/select"; +import { RadioGroup, RadioGroupItem } from "src/components/motion/radio"; +import { Tooltip } from "src/components/motion/tooltip"; +import { BouncyAccordion } from "src/components/motion/bouncy-accordion"; +import { AnimatedBadge } from "src/components/motion/animated-badge"; +import { getAccountStorageKey } from "../utils"; +import { t } from "../../../lib/i18n"; interface SettingsProps { isOpen: boolean; @@ -25,79 +39,666 @@ interface EmailAccount { interface AppSettings { customPresets: CustomPreset[]; maxHistory: number; - theme: 'light' | 'dark' | 'auto'; + theme: "light" | "dark" | "auto"; showNotifications: boolean; - badgeDisplay: 'none' | 'total' | 'today' | 'week' | 'all-time'; - randomFormat: 'private-mail' | 'alphanumeric' | 'words' | 'timestamp'; + badgeDisplay: "none" | "total" | "today" | "week" | "all-time"; + randomFormat: "private-mail" | "alphanumeric" | "words" | "timestamp"; +} + +interface ConfirmationRequest { + title: string; + message: string; + confirmLabel: string; + variant?: "primary" | "danger"; + resolve: (confirmed: boolean) => void; +} + +interface ChangelogChange { + type: "Added" | "Changed" | "Fixed"; + items: string[]; +} + +interface ChangelogEntry { + version: string; + date: string; + changes: ChangelogChange[]; } const DEFAULT_SETTINGS: AppSettings = { customPresets: [], maxHistory: 20, - theme: 'light', + theme: "light", showNotifications: true, - badgeDisplay: 'all-time', - randomFormat: 'private-mail', + badgeDisplay: "all-time", + randomFormat: "private-mail", }; -// Helper to get account-specific storage key -const getAccountStorageKey = (email: string, suffix: string) => { - const sanitized = email.replace(/[^a-zA-Z0-9]/g, '_'); - return `${suffix}_${sanitized}`; -}; +const CHANGELOG: ChangelogEntry[] = [ + { + version: "1.2.0", + date: "2026-07-03", + changes: [ + { + type: "Added", + items: [ + "Added Tailwind CSS v4, shadcn, and beUI motion components", + "Added Action Swap, Animated Badge, Bouncy Accordion, Theme Toggle, Tooltip, and Table integrations", + "Added dark mode toggle in the popup header", + "Added locale key coverage tests for all supported languages", + ], + }, + { + type: "Changed", + items: [ + "Redesigned popup, settings, generator tabs, Gmail tricks, history table, and changelog UI with a unified beUI style", + "Reworked Recent Aliases into a compact non-scrolling table with fixed action buttons", + "Improved dark mode contrast, spacing, hover states, tooltips, and responsive popup layout", + "Moved theme switching out of Settings and into the main popup header", + "Updated all supported locales with the new UI strings", + ], + }, + { + type: "Fixed", + items: [ + '"Copy All" no longer undercounts statistics for generated aliases', + "Settings/QR modals no longer render outside the popup bounds", + "Tab key now moves focus normally instead of being hijacked for @gmail.com autocomplete", + "Fixed missing imports and old component references after replacing legacy UI components", + "Fixed table overflow and hidden row action buttons in alias history", + "Fixed untranslated/fallback strings in the new UI", + ], + }, + ], + }, + { + version: "1.1.0", + date: "2025-12-30", + changes: [ + { + type: "Added", + items: [ + "Gmail alias generation with plus addressing", + "Preset management", + "Keyboard shortcuts", + "Statistics tracking", + ], + }, + { type: "Changed", items: ["Updated dependencies"] }, + { type: "Fixed", items: ["Bug fixes and improvements"] }, + ], + }, + { + version: "1.0.0", + date: "2025-12-30", + changes: [{ type: "Added", items: ["Initial release"] }], + }, +]; + +interface SettingsPanelProps { + settings: AppSettings; + saveSettings: (settings: AppSettings) => Promise; +} + +/** Appearance and display settings shown inside the general accordion. */ +function AppearanceSettingsPanel({ + settings, + saveSettings, +}: SettingsPanelProps) { + return ( +
+ + +
+ ); +} + +/** Badge counter setting field. */ +function BadgeCounterField({ settings, saveSettings }: SettingsPanelProps) { + return ( +
+ + +
+ ); +} + +/** Badge counter select options. */ +function BadgeCounterOptions() { + return ( + + None (Hidden) + Total in History + Total Generated (All Time) + Created Today + This Week + + ); +} + +/** Copy notification toggle field. */ +function CopyNotificationsField({ + settings, + saveSettings, +}: SettingsPanelProps) { + return ( +
+ + + saveSettings({ ...settings, showNotifications: enabled }) + } + label="" + /> +
+ ); +} + +/** Alias generation defaults shown inside the general accordion. */ +function AliasGenerationSettingsPanel({ + settings, + saveSettings, +}: SettingsPanelProps) { + return ( +
+ + +
+ ); +} -export default function Settings({ isOpen, onClose, onClearHistory }: SettingsProps) { +/** Random alias format setting field. */ +function RandomAliasFormatField({ + settings, + saveSettings, +}: SettingsPanelProps) { + return ( +
+ + +
+ ); +} + +/** Random alias format select options. */ +function RandomAliasFormatOptions() { + return ( + + + Private Mail (e.g., private-mail-q2ga) + + + Random Characters (e.g., abc123xy) + + Random Words (e.g., happy-fox-42) + Timestamp (e.g., lk9x2m3n) + + ); +} + +/** Auto-save limit setting field. */ +function AutoSaveLimitField({ settings, saveSettings }: SettingsPanelProps) { + return ( +
+ + +
+ ); +} + +/** Auto-save limit select options. */ +function AutoSaveLimitOptions() { + return ( + + 20 aliases + 50 aliases + 100 aliases + 200 aliases + 500 aliases + + ); +} + +interface AddAccountCardProps { + showAddAccount: boolean; + newAccountEmail: string; + newAccountLabel: string; + addAccountError: string; + focusOnMount: (el: HTMLInputElement | null) => void; + setShowAddAccount: (show: boolean) => void; + setNewAccountEmail: (value: string) => void; + setNewAccountLabel: (value: string) => void; + setAddAccountError: (value: string) => void; + handleAddAccount: () => void; +} + +/** Account creation card used by the settings accounts tab. */ +function AddAccountCard({ + showAddAccount, + newAccountEmail, + newAccountLabel, + addAccountError, + focusOnMount, + setShowAddAccount, + setNewAccountEmail, + setNewAccountLabel, + setAddAccountError, + handleAddAccount, +}: AddAccountCardProps) { + /** Resets and hides the add-account form. */ + const closeForm = () => { + setShowAddAccount(false); + setNewAccountEmail(""); + setNewAccountLabel(""); + setAddAccountError(""); + }; + + return ( +
+ {!showAddAccount ? ( + setShowAddAccount(true)} /> + ) : ( +
+ + + {addAccountError && } + {newAccountEmail && !newAccountEmail.includes("@") && } + + +
+ )} +
+ ); +} + +/** Button that opens the add-account form. */ +function OpenAddAccountButton({ onClick }: { onClick: () => void }) { + return ( + + ); +} + +/** Header row for the add-account form. */ +function AddAccountFormHeader({ onClose }: { onClose: () => void }) { + return ( +
+

+ {t("addNewAccountTitle")} +

+ + + +
+ ); +} + +interface AccountEmailInputProps { + value: string; + setValue: (value: string) => void; + setError: (value: string) => void; + onEnter: () => void; + focusOnMount: (el: HTMLInputElement | null) => void; +} + +/** Email input with Gmail suffix preview for username-only entries. */ +function AccountEmailInput({ + value, + setValue, + setError, + onEnter, + focusOnMount, +}: AccountEmailInputProps) { + return ( +
+ { + setValue(nextValue); + setError(""); + }} + onKeyDown={(e) => { + if (e.key === "Enter") onEnter(); + }} + onBlur={() => { + if (value && !value.includes("@")) setValue(`${value}@gmail.com`); + }} + placeholder={t("emailPlaceholder")} + ref={focusOnMount} + /> + {value && !value.includes("@") && ( +
+ @gmail.com +
+ )} +
+ ); +} + +/** Validation message for the add-account card. */ +function AccountErrorMessage({ message }: { message: string }) { + return ( +
+

{message}

+
+ ); +} + +/** Hint shown when a bare Gmail username is entered. */ +function GmailHint() { + return ( +

+ Press{" "} + + Tab + {" "} + to add @gmail.com +

+ ); +} + +/** Changelog tab content rendered from static release data. */ +function ChangelogPanel() { + return ( +
+ {CHANGELOG.map((entry) => ( + + ))} +
+ ); +} + +/** Single changelog release card. */ +function ChangelogCard({ entry }: { entry: ChangelogEntry }) { + return ( +
+ +
+ {entry.changes.map((change) => ( + + ))} +
+
+ ); +} + +/** Header for one changelog release card. */ +function ChangelogCardHeader({ entry }: { entry: ChangelogEntry }) { + return ( +
+
+ + + + + + + v{entry.version} + +
+ + {entry.date} + +
+ ); +} + +interface ChangelogChangeGroupProps { + change: ChangelogChange; + version: string; +} + +/** One grouped change type inside a changelog card. */ +function ChangelogChangeGroup({ change, version }: ChangelogChangeGroupProps) { + return ( +
+
+ + + {change.type} + +
+
    + {change.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ ); +} + +/** Dot color for changelog change type. */ +function changeDotClass(type: ChangelogChange["type"]) { + if (type === "Added") return "bg-emerald-500"; + if (type === "Fixed") return "bg-rose-500"; + return "bg-primary"; +} + +/** Animated badge status for changelog change type. */ +function changeBadgeStatus(type: ChangelogChange["type"]) { + if (type === "Added") return "success"; + if (type === "Fixed") return "danger"; + return "info"; +} + +/** Settings modal with general, accounts, presets, advanced, and changelog tabs. */ +export default function Settings({ + isOpen, + onClose, + onClearHistory, +}: SettingsProps) { const [settings, setSettings] = useState(DEFAULT_SETTINGS); - const [newPresetLabel, setNewPresetLabel] = useState(''); - const [newPresetTag, setNewPresetTag] = useState(''); - const [activeTab, setActiveTab] = useState<'general' | 'accounts' | 'presets' | 'advanced'>('general'); + const [newPresetLabel, setNewPresetLabel] = useState(""); + const [newPresetTag, setNewPresetTag] = useState(""); + const [activeTab, setActiveTab] = useState< + "general" | "accounts" | "presets" | "advanced" | "changelog" + >("general"); const [emailAccounts, setEmailAccounts] = useState([]); const [editingAccountId, setEditingAccountId] = useState(null); - const [editingLabel, setEditingLabel] = useState(''); - const [editingEmail, setEditingEmail] = useState(''); - const [version, setVersion] = useState('1.1.0'); + const [editingLabel, setEditingLabel] = useState(""); + const [editingEmail, setEditingEmail] = useState(""); + const [version, setVersion] = useState("1.1.0"); const [showAddAccount, setShowAddAccount] = useState(false); - const [newAccountEmail, setNewAccountEmail] = useState(''); - const [newAccountLabel, setNewAccountLabel] = useState(''); - const [addAccountError, setAddAccountError] = useState(''); + const [newAccountEmail, setNewAccountEmail] = useState(""); + const [newAccountLabel, setNewAccountLabel] = useState(""); + const [addAccountError, setAddAccountError] = useState(""); + const [toast, setToast] = useState(null); + const [confirmation, setConfirmation] = useState( + null, + ); + + const showToast = useCallback((msg: string) => { + setToast(msg); + setTimeout(() => setToast(null), 2000); + }, []); + + const askForConfirmation = useCallback( + (request: Omit) => + new Promise((resolve) => { + setConfirmation({ ...request, resolve }); + }), + [], + ); + + const closeConfirmation = useCallback((confirmed: boolean) => { + setConfirmation((current) => { + current?.resolve(confirmed); + return null; + }); + }, []); useEffect(() => { try { const manifest = browser.runtime.getManifest(); - if (manifest && manifest.version) { + if (manifest?.version) { setVersion(manifest.version); } } catch (error) { - console.log('Could not get manifest version:', error); + console.log("Could not get manifest version:", error); } }, []); - useEffect(() => { - if (isOpen) { - loadSettings(); - loadAccounts(); - } - }, [isOpen]); - + /** Loads saved app settings from extension storage, merged over defaults. */ const loadSettings = async () => { - const result = await browser.storage.local.get('app_settings'); + const result = await browser.storage.local.get("app_settings"); if (result.app_settings) { setSettings({ ...DEFAULT_SETTINGS, ...result.app_settings }); } }; + /** Loads the email accounts list from extension storage. */ const loadAccounts = async () => { - const result = await browser.storage.local.get('email_accounts'); + const result = await browser.storage.local.get("email_accounts"); if (result.email_accounts && Array.isArray(result.email_accounts)) { setEmailAccounts(result.email_accounts); } }; + useEffect(() => { + if (isOpen) { + loadSettings(); + loadAccounts(); + } + }, [isOpen]); + + /** Persists settings to extension storage and updates local state. */ const saveSettings = async (newSettings: AppSettings) => { setSettings(newSettings); await browser.storage.local.set({ app_settings: newSettings }); }; + /** Adds a new custom preset from the label/tag form fields. */ const handleAddPreset = () => { if (!newPresetLabel.trim() || !newPresetTag.trim()) return; @@ -113,33 +714,39 @@ export default function Settings({ isOpen, onClose, onClearHistory }: SettingsPr }; saveSettings(updatedSettings); - setNewPresetLabel(''); - setNewPresetTag(''); + setNewPresetLabel(""); + setNewPresetTag(""); + showToast(t("toastPresetAdded")); }; + /** Removes a custom preset by id. */ const handleRemovePreset = (id: string) => { const updatedSettings = { ...settings, customPresets: settings.customPresets.filter((p) => p.id !== id), }; saveSettings(updatedSettings); + showToast(t("toastPresetRemoved")); }; + /** Downloads the current settings as a JSON file. */ const handleExportSettings = () => { const dataStr = JSON.stringify(settings, null, 2); - const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const dataBlob = new Blob([dataStr], { type: "application/json" }); const url = URL.createObjectURL(dataBlob); - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; link.download = `gmail-alias-settings-${Date.now()}.json`; link.click(); URL.revokeObjectURL(url); + showToast(t("toastSettingsExported")); }; + /** Imports settings from a user-selected JSON file. */ const handleImportSettings = () => { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.json'; + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; input.onchange = async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; @@ -148,51 +755,96 @@ export default function Settings({ isOpen, onClose, onClearHistory }: SettingsPr const text = await file.text(); const imported = JSON.parse(text); saveSettings({ ...DEFAULT_SETTINGS, ...imported }); - } catch (err) { - alert('Failed to import settings. Please check the file format.'); + showToast(t("toastSettingsImported")); + } catch { + showToast(t("toastImportFailed")); } }; input.click(); }; - const handleResetSettings = () => { - if (confirm('Are you sure you want to reset all settings to default?')) { - saveSettings(DEFAULT_SETTINGS); + /** Focuses an input once when it mounts (replaces autoFocus). */ + const focusOnMount = useCallback((el: HTMLInputElement | null) => { + el?.focus(); + }, []); + + /** Resets all settings to defaults after user confirmation. */ + const handleResetSettings = async () => { + const confirmed = await askForConfirmation({ + title: t("resetSettingsTitle"), + message: t("resetSettingsMessage"), + confirmLabel: t("reset"), + variant: "danger", + }); + + if (!confirmed) return; + + saveSettings(DEFAULT_SETTINGS); + showToast(t("toastSettingsReset")); + }; + + /** Clears recent aliases after user confirmation. */ + const handleClearHistory = async () => { + const confirmed = await askForConfirmation({ + title: t("clearHistoryTitle"), + message: t("clearHistoryMessage"), + confirmLabel: t("clear"), + variant: "danger", + }); + + if (confirmed) { + onClearHistory(); } }; + /** Marks the given account as active and updates the base email. */ const handleSwitchAccount = async (accountId: string) => { - const updated = emailAccounts.map(acc => ({ + const updated = emailAccounts.map((acc) => ({ ...acc, isActive: acc.id === accountId, })); await browser.storage.local.set({ email_accounts: updated }); - const activeAccount = updated.find(acc => acc.id === accountId); + const activeAccount = updated.find((acc) => acc.id === accountId); if (activeAccount) { await browser.storage.local.set({ base_email: activeAccount.email }); } setEmailAccounts(updated); + showToast(t("toastAccountSwitched")); }; + /** Deletes an account and all of its stored data after confirmation. */ const handleDeleteAccount = async (account: EmailAccount) => { if (emailAccounts.length === 1) { - alert('Cannot delete the last account. You must have at least one account.'); + showToast(t("cannotDeleteLastAccountTitle")); return; } - const confirmMsg = `Delete "${account.label}" (${account.email})?\n\nThis will permanently delete:\n• All history for this account\n• All statistics\n• All favorites\n\nThis action cannot be undone.`; - - if (!confirm(confirmMsg)) return; + const confirmMsg = t("deleteAccountMessage", [ + account.label, + account.email, + ]); + + const confirmed = await askForConfirmation({ + title: t("deleteAccountTitle"), + message: confirmMsg, + confirmLabel: t("delete"), + variant: "danger", + }); + + if (!confirmed) return; // Delete account-specific data - const historyKey = getAccountStorageKey(account.email, 'gmail_alias_recent'); - const statsKey = getAccountStorageKey(account.email, 'alias_stats'); - const favoritesKey = getAccountStorageKey(account.email, 'favorites'); + const historyKey = getAccountStorageKey( + account.email, + "gmail_alias_recent", + ); + const statsKey = getAccountStorageKey(account.email, "alias_stats"); + const favoritesKey = getAccountStorageKey(account.email, "favorites"); await browser.storage.local.remove([historyKey, statsKey, favoritesKey]); // Remove from accounts list - let updated = emailAccounts.filter(acc => acc.id !== account.id); + let updated = emailAccounts.filter((acc) => acc.id !== account.id); // If we deleted the active account, make the first one active if (account.isActive && updated.length > 0) { @@ -205,32 +857,36 @@ export default function Settings({ isOpen, onClose, onClearHistory }: SettingsPr await browser.storage.local.set({ email_accounts: updated }); setEmailAccounts(updated); + showToast(t("toastAccountDeleted")); }; + /** Enters edit mode for the given account. */ const handleStartEdit = (account: EmailAccount) => { setEditingAccountId(account.id); setEditingLabel(account.label); setEditingEmail(account.email); }; + /** Exits edit mode without saving. */ const handleCancelEdit = () => { setEditingAccountId(null); - setEditingLabel(''); - setEditingEmail(''); + setEditingLabel(""); + setEditingEmail(""); }; + /** Saves account edits, migrating stored data if the email changed. */ const handleSaveEdit = async (accountId: string) => { if (!editingLabel.trim()) { - alert('Label cannot be empty'); + showToast(t("errorLabelRequired")); return; } - if (!editingEmail.trim() || !editingEmail.includes('@')) { - alert('Please enter a valid email address'); + if (!editingEmail.trim() || !editingEmail.includes("@")) { + showToast(t("errorInvalidEmail")); return; } - const account = emailAccounts.find(acc => acc.id === accountId); + const account = emailAccounts.find((acc) => acc.id === accountId); if (!account) return; const oldEmail = account.email; @@ -239,27 +895,47 @@ export default function Settings({ isOpen, onClose, onClearHistory }: SettingsPr // Check if email changed if (oldEmail !== newEmail) { // Check if new email already exists in another account - const emailExists = emailAccounts.some(acc => acc.id !== accountId && acc.email === newEmail); + const emailExists = emailAccounts.some( + (acc) => + acc.id !== accountId && + acc.email.toLowerCase() === newEmail.toLowerCase(), + ); if (emailExists) { - alert('This email address is already used by another account!'); + showToast(t("errorDuplicateEmail")); return; } - const confirmMsg = `Change email from\n${oldEmail}\nto\n${newEmail}?\n\nThis will:\n• Migrate all history, statistics, and favorites to the new email\n• Update the account email\n• Delete data associated with the old email\n\nContinue?`; - - if (!confirm(confirmMsg)) return; + const confirmMsg = t("changeAccountEmailMessage", [oldEmail, newEmail]); - // Migrate data from old email to new email - const oldHistoryKey = getAccountStorageKey(oldEmail, 'gmail_alias_recent'); - const oldStatsKey = getAccountStorageKey(oldEmail, 'alias_stats'); - const oldFavoritesKey = getAccountStorageKey(oldEmail, 'favorites'); + const confirmed = await askForConfirmation({ + title: t("changeAccountEmailTitle"), + message: confirmMsg, + confirmLabel: t("changeEmail"), + }); + + if (!confirmed) return; - const newHistoryKey = getAccountStorageKey(newEmail, 'gmail_alias_recent'); - const newStatsKey = getAccountStorageKey(newEmail, 'alias_stats'); - const newFavoritesKey = getAccountStorageKey(newEmail, 'favorites'); + // Migrate data from old email to new email + const oldHistoryKey = getAccountStorageKey( + oldEmail, + "gmail_alias_recent", + ); + const oldStatsKey = getAccountStorageKey(oldEmail, "alias_stats"); + const oldFavoritesKey = getAccountStorageKey(oldEmail, "favorites"); + + const newHistoryKey = getAccountStorageKey( + newEmail, + "gmail_alias_recent", + ); + const newStatsKey = getAccountStorageKey(newEmail, "alias_stats"); + const newFavoritesKey = getAccountStorageKey(newEmail, "favorites"); // Get old data - const oldData = await browser.storage.local.get([oldHistoryKey, oldStatsKey, oldFavoritesKey]); + const oldData = await browser.storage.local.get([ + oldHistoryKey, + oldStatsKey, + oldFavoritesKey, + ]); // Save to new keys await browser.storage.local.set({ @@ -269,7 +945,11 @@ export default function Settings({ isOpen, onClose, onClearHistory }: SettingsPr }); // Delete old keys - await browser.storage.local.remove([oldHistoryKey, oldStatsKey, oldFavoritesKey]); + await browser.storage.local.remove([ + oldHistoryKey, + oldStatsKey, + oldFavoritesKey, + ]); // Update base_email if this is the active account if (account.isActive) { @@ -278,486 +958,615 @@ export default function Settings({ isOpen, onClose, onClearHistory }: SettingsPr } // Update account in list - const updated = emailAccounts.map(acc => - acc.id === accountId ? { ...acc, label: editingLabel.trim(), email: editingEmail.trim() } : acc + const updated = emailAccounts.map((acc) => + acc.id === accountId + ? { ...acc, label: editingLabel.trim(), email: editingEmail.trim() } + : acc, ); - + await browser.storage.local.set({ email_accounts: updated }); setEmailAccounts(updated); setEditingAccountId(null); - setEditingLabel(''); - setEditingEmail(''); + setEditingLabel(""); + setEditingEmail(""); + showToast(t("toastAccountUpdated")); }; + /** Validates and adds a new email account. */ const handleAddAccount = async () => { let email = newAccountEmail.trim(); - + if (!email) { - setAddAccountError('Please enter an email address'); + setAddAccountError(t("errorEnterEmail")); return; } // Auto-add @gmail.com if only username provided - if (!email.includes('@')) { - email += '@gmail.com'; + if (!email.includes("@")) { + email += "@gmail.com"; } // Validate email format - if (!email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) { - setAddAccountError('Please enter a valid email address'); + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + setAddAccountError(t("errorInvalidEmail")); return; } // Check if account already exists - const exists = emailAccounts.some(acc => acc.email === email); + const exists = emailAccounts.some((acc) => acc.email === email); if (exists) { - setAddAccountError('This account already exists'); + setAddAccountError(t("errorAccountExists")); return; } - // Create new account + // Create new account — only auto-activate if it's the first account + const isFirst = emailAccounts.length === 0; const newAccount: EmailAccount = { id: Date.now().toString(), email, - label: newAccountLabel.trim() || email.split('@')[0], - isActive: emailAccounts.length === 0, // Make first account active + label: newAccountLabel.trim() || email.split("@")[0], + isActive: isFirst, }; - const updated = emailAccounts.length === 0 - ? [newAccount] - : [...emailAccounts.map(acc => ({ ...acc, isActive: false })), newAccount]; - - // Make new account active - updated[updated.length - 1].isActive = true; + const updated = isFirst ? [newAccount] : [...emailAccounts, newAccount]; - await browser.storage.local.set({ + await browser.storage.local.set({ email_accounts: updated, - base_email: newAccount.email + ...(isFirst ? { base_email: newAccount.email } : {}), }); setEmailAccounts(updated); setShowAddAccount(false); - setNewAccountEmail(''); - setNewAccountLabel(''); - setAddAccountError(''); + setNewAccountEmail(""); + setNewAccountLabel(""); + setAddAccountError(""); + showToast(t("toastAccountAdded", newAccount.label)); }; if (!isOpen) return null; + // skipcq: JS-0415 return ( -
-
+ // skipcq: JS-0415 +
+
{/* Header */} -
-
- - - - -

Settings

+
+
+ + + +

{t("settings")}

- + + v{version} +
{/* Tabs */} -
-
- - +
- + {t("accounts")} + + +
{/* Content */} -
+
{/* General Tab */} - {activeTab === 'general' && ( -
- {/* Appearance Section */} -
-

- - - - Appearance & Display -

-
-
- - -
- -
- -

Display count on extension icon

- -
- -
-
- -

Copy confirmation messages

-
- saveSettings({ ...settings, showNotifications: enabled })} - label="" + {activeTab === "general" && ( + // skipcq: JS-0415 + + + + + + ), + description: ( + -
-
-
- - {/* Alias Generation Section */} -
-

- - - - Alias Generation -

-
-
- - -

Choose the format for random alias generation

-
- -
- - -

Maximum number of aliases to auto-save to history

-
-
-
- - {/* Custom Presets Section */} -
-

- - - - Custom Presets -

-
- - - -
- - {settings.customPresets.length > 0 && ( -
-
Your Presets ({settings.customPresets.length})
-
- {settings.customPresets.map((preset) => ( -
+ + + + + ), + description: ( + + ), + }, + { + id: "custom-presets", + title: t("customPresets"), + icon: ( + + + + + + ), + description: ( +
+
+ + +
+ + {settings.customPresets.length > 0 && ( +
+ {settings.customPresets.map((preset) => ( +
+
+ + {preset.label} + + + +{preset.tag} + +
+ + + +
+ ))} +
+ )} +
+ ), + }, + { + id: "data-management", + title: t("dataManagement"), + icon: ( + + + + + + ), + description: ( +
+
+ -
-
{preset.label}
-
+{preset.tag}
-
- -
- ))} + {t("export")} + + + + + + + + +
+ + +
-
- )} -
- - {/* Data Management Section */} -
-

- - - - Data Management -

-
-
- - -
- -
-
- - {/* Danger Zone */} -
-

- - - - Danger Zone -

-

This action cannot be undone

- -
-
+ ), + }, + ]} + /> )} {/* Accounts Tab */} - {activeTab === 'accounts' && ( + {activeTab === "accounts" && (
-

Email Accounts

-

- Manage your Gmail accounts. Each account has its own history, statistics, and favorites. +

+ {t("emailAccounts")} +

+

+ {t("manageAccountsDescription")}

{emailAccounts.length === 0 ? ( -
- No accounts found. Please add an account from the main screen. +
+ {t("noAccountsFound")}
) : (
{emailAccounts.map((account) => (
{editingAccountId === account.id ? ( // Edit mode
- - + {t("label")} + + setEditingLabel(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="Account label" - autoFocus + onChange={setEditingLabel} + placeholder={t("accountLabel")} + ref={focusOnMount} />
- - + {t("emailAddress")} + + setEditingEmail(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono" - placeholder="your.email@gmail.com" + onChange={setEditingEmail} + placeholder={t("emailAddressPlaceholder")} /> {editingEmail !== account.email && ( -

- ⚠️ Changing email will migrate all data to the new email address +

+ {t("emailChangeWarning")}

)}
- - + {t("cancel")} +
) : ( // View mode + // skipcq: JS-0415
{/* Radio button to select active account */}
)} - {/* Add Account Section */} -
- {!showAddAccount ? ( - - ) : ( -
-
-

Add New Account

- -
- -
- { - setNewAccountEmail(e.target.value); - setAddAccountError(''); - }} - onKeyDown={(e) => { - if (e.key === 'Tab' && newAccountEmail && !newAccountEmail.includes('@')) { - e.preventDefault(); - setNewAccountEmail(newAccountEmail + '@gmail.com'); - } - if (e.key === 'Enter') { - handleAddAccount(); - } - }} - placeholder="your.email" - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" - autoFocus - /> - {newAccountEmail && !newAccountEmail.includes('@') && ( -
- @gmail.com -
- )} -
- - {addAccountError && ( -
-

{addAccountError}

-
- )} - -

- 💡 Press Tab to add @gmail.com -

- - setNewAccountLabel(e.target.value)} - placeholder="Label (optional, e.g., Work, Personal)" - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - - -
- )} -
+
)} + {/* Changelog Tab */} + {activeTab === "changelog" && }
- {/* Footer */} -
-
- - - - Gmail Alias Toolkit - - v{version} +
+
+ + {t("extensionName")} + + • + + + v{version} +
+ + {/* Settings Toast - inside modal so it shows above the overlay */} + {toast && ( +
+ {toast} +
+ )} + + {confirmation && ( + closeConfirmation(false)} + onConfirm={() => closeConfirmation(true)} + /> + )} +
+ ); +} + +interface ConfirmationDialogProps { + request: ConfirmationRequest; + onCancel: () => void; + onConfirm: () => void; +} + +/** Renders the blocking confirmation prompt used by destructive settings actions. */ +function ConfirmationDialog({ + request, + onCancel, + onConfirm, +}: ConfirmationDialogProps) { + const confirmClass = + request.variant === "danger" + ? "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus:ring-ring" + : "bg-primary text-primary-foreground hover:bg-primary/90 focus:ring-ring"; + + return ( +
+
+

{request.title}

+

+ {request.message} +

+
+ + +
+
); } diff --git a/entrypoints/popup/components/Statistics.tsx b/entrypoints/popup/components/Statistics.tsx index 0b74625..99d481a 100644 --- a/entrypoints/popup/components/Statistics.tsx +++ b/entrypoints/popup/components/Statistics.tsx @@ -1,4 +1,9 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react"; +import type { ReactNode } from "react"; +import { BarChart3, Check, Clock, Mail, X } from "lucide-react"; +import { AnimatedNumber } from "src/components/motion/animated-number"; +import Button from "./Button"; +import { getAccountStorageKey } from "../utils"; interface Stats { totalGenerated: number; @@ -7,83 +12,102 @@ interface Stats { createdThisWeek: number; } -// Helper to get account-specific storage key -const getAccountStorageKey = (email: string, suffix: string) => { - const sanitized = email.replace(/[^a-zA-Z0-9]/g, '_'); - return `${suffix}_${sanitized}`; -}; +interface StoredAccount { + email: string; + isActive: boolean; +} + +interface RecentAlias { + timestamp: number; +} + +type StorageChanges = Record< + string, + { newValue?: unknown; oldValue?: unknown } +>; + +interface StatCardProps { + icon: ReactNode; + value: ReactNode; + label: string; +} + +/** Compact metric tile used inside the statistics panel. */ +function StatCard({ icon, value, label }: StatCardProps) { + return ( +
+
+ {icon} +
+
{value}
+

+ {label} +

+
+ ); +} +/** Collapsible panel showing alias usage statistics for the active account. */ export default function Statistics() { const [stats, setStats] = useState({ totalGenerated: 0, - mostUsedTag: '-', + mostUsedTag: "-", createdToday: 0, createdThisWeek: 0, }); const [isOpen, setIsOpen] = useState(false); - const [activeEmail, setActiveEmail] = useState('your.email@gmail.com'); - - useEffect(() => { - loadActiveEmailAndStats(); - - // Listen for storage changes - const handleStorageChange = (changes: any) => { - // Reload if any account-specific storage key changes or if email_accounts changes - if (changes.email_accounts) { - loadActiveEmailAndStats(); - } else { - // Check if any changed key starts with our prefixes - const changedKeys = Object.keys(changes); - const relevantChange = changedKeys.some(key => - key.startsWith('gmail_alias_recent_') || - key.startsWith('alias_stats_') - ); - if (relevantChange) { - loadActiveEmailAndStats(); - } - } - }; - - browser.storage.onChanged.addListener(handleStorageChange); - return () => browser.storage.onChanged.removeListener(handleStorageChange); - }, []); + /** Resolves the active account email, then loads and computes its stats. */ const loadActiveEmailAndStats = async () => { // Get active account first - const accountResult = await browser.storage.local.get(['email_accounts', 'base_email']); - let email = 'your.email@gmail.com'; - - if (accountResult.email_accounts && Array.isArray(accountResult.email_accounts)) { - const activeAccount = accountResult.email_accounts.find((acc: any) => acc.isActive); + const accountResult = await browser.storage.local.get([ + "email_accounts", + "base_email", + ]); + let email = "your.email@gmail.com"; + + if ( + accountResult.email_accounts && + Array.isArray(accountResult.email_accounts) + ) { + const activeAccount = ( + accountResult.email_accounts as StoredAccount[] + ).find((acc) => acc.isActive); if (activeAccount) { email = activeAccount.email; } } else if (accountResult.base_email) { - email = accountResult.base_email; + email = accountResult.base_email as string; } - - setActiveEmail(email); - + // Load stats for this account - const historyKey = getAccountStorageKey(email, 'gmail_alias_recent'); - const statsKey = getAccountStorageKey(email, 'alias_stats'); - + const historyKey = getAccountStorageKey(email, "gmail_alias_recent"); + const statsKey = getAccountStorageKey(email, "alias_stats"); + const result = await browser.storage.local.get([historyKey, statsKey]); - const recent = (result[historyKey] || []) as any[]; - const savedStats = (result[statsKey] || { total: 0, tags: {} }) as { total: number; tags: Record }; + const recent = (result[historyKey] || []) as RecentAlias[]; + const savedStats = (result[statsKey] || { total: 0, tags: {} }) as { + total: number; + tags: Record; + }; const now = new Date(); - const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); + const today = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + ).getTime(); const weekAgo = today - 7 * 24 * 60 * 60 * 1000; - const createdToday = recent.filter((a: any) => a.timestamp >= today).length; - const createdThisWeek = recent.filter((a: any) => a.timestamp >= weekAgo).length; + const createdToday = recent.filter((a) => a.timestamp >= today).length; + const createdThisWeek = recent.filter((a) => a.timestamp >= weekAgo).length; // Find most used tag const tags = savedStats.tags || {}; - const mostUsedTag = Object.keys(tags).length > 0 - ? Object.entries(tags).sort((a: any, b: any) => b[1] - a[1])[0][0] - : '-'; + const mostUsedTag = + Object.keys(tags).length > 0 + ? Object.entries(tags).sort((a, b) => b[1] - a[1])[0][0] + : "-"; setStats({ totalGenerated: savedStats.total || 0, @@ -93,56 +117,84 @@ export default function Statistics() { }); }; + useEffect(() => { + loadActiveEmailAndStats(); + + /** Reloads stats when account or alias storage keys change. */ + const handleStorageChange = (changes: StorageChanges) => { + // Reload if any account-specific storage key changes or if email_accounts changes + if (changes.email_accounts) { + loadActiveEmailAndStats(); + } else { + // Check if any changed key starts with our prefixes + const changedKeys = Object.keys(changes); + const relevantChange = changedKeys.some( + (key) => + key.startsWith("gmail_alias_recent_") || + key.startsWith("alias_stats_"), + ); + if (relevantChange) { + loadActiveEmailAndStats(); + } + } + }; + + browser.storage.onChanged.addListener(handleStorageChange); + return () => browser.storage.onChanged.removeListener(handleStorageChange); + }, []); + if (!isOpen) { return ( - + + View Statistics + + + ); } + // skipcq: JS-0415 return ( -
-
-

Statistics

- + +
-
-
-
{stats.totalGenerated}
-
Total Generated
-
- -
-
{stats.createdToday}
-
Created Today
-
- -
-
{stats.createdThisWeek}
-
This Week
-
- -
-
{stats.mostUsedTag}
-
Most Used Tag
-
+
+ } + value={} + label="Total Generated" + /> + } + value={} + label="Created Today" + /> + } + value={} + label="This Week" + /> + } + value={stats.mostUsedTag} + label="Top Tag" + />
); diff --git a/entrypoints/popup/components/Toggle.tsx b/entrypoints/popup/components/Toggle.tsx index 19494c8..14c4a51 100644 --- a/entrypoints/popup/components/Toggle.tsx +++ b/entrypoints/popup/components/Toggle.tsx @@ -1,31 +1,27 @@ -interface ToggleProps { +import { Switch } from "../../../src/components/motion/switch"; + +export interface ToggleProps { enabled: boolean; onChange: (enabled: boolean) => void; label: string; description?: string; } -export default function Toggle({ enabled, onChange, label, description }: ToggleProps) { +export default function Toggle({ + enabled, + onChange, + label, + description, +}: ToggleProps) { return ( -
-
- - {description &&

{description}

} +
+
+

{label}

+ {description ? ( +

{description}

+ ) : null}
- +
); } diff --git a/entrypoints/popup/components/WelcomeScreen.tsx b/entrypoints/popup/components/WelcomeScreen.tsx index 14d730d..2a19be6 100644 --- a/entrypoints/popup/components/WelcomeScreen.tsx +++ b/entrypoints/popup/components/WelcomeScreen.tsx @@ -1,86 +1,99 @@ -import { useState } from 'react'; +import { useCallback, useState, ReactNode } from "react"; +import { UserRound } from "lucide-react"; +import Button from "./Button"; +import Input from "./Input"; +import { TextReveal } from "src/components/motion/text-reveal"; +import { + validateEmail as validateEmailPure, + getAccountStorageKey, +} from "../utils"; +import { t } from "../../../lib/i18n"; interface WelcomeScreenProps { onEmailAdded: (email: string) => void; onOpenSettings: () => void; } -export default function WelcomeScreen({ onEmailAdded, onOpenSettings }: WelcomeScreenProps) { - const [email, setEmail] = useState(''); +interface WelcomeFormProps { + email: string; + validationError: string; + isSubmitting: boolean; + onEmailChange: (value: string) => void; + onBlur: () => void; + onKeyPress: (e: React.KeyboardEvent) => void; + onSubmit: () => void; + onOpenSettings: () => void; + focusOnMount: (el: HTMLInputElement | null) => void; +} + +interface WelcomeCardProps { + children: ReactNode; +} + +/** First-run screen that collects the user's base email and creates the initial account. */ +export default function WelcomeScreen({ + onEmailAdded, + onOpenSettings, +}: WelcomeScreenProps) { + const [email, setEmail] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); - const [validationError, setValidationError] = useState(''); + const [validationError, setValidationError] = useState(""); + /** Focuses the email input once when it mounts (replaces autoFocus). */ + const focusOnMount = useCallback((el: HTMLInputElement | null) => { + el?.focus(); + }, []); + + /** Validates the email, storing any error or warning message in state. */ const validateEmail = (value: string): boolean => { - setValidationError(''); - - if (!value.trim()) { - setValidationError('Email is required'); - return false; - } - - if (!value.includes('@')) { - setValidationError('Please enter a valid email address'); - return false; - } - - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(value)) { - setValidationError('Invalid email format'); - return false; - } - - const [username, domain] = value.split('@'); - - if (username.length < 1) { - setValidationError('Username cannot be empty'); - return false; - } - - if (!domain.includes('.')) { - setValidationError('Domain must include a dot (e.g., gmail.com)'); + setValidationError(""); + const result = validateEmailPure(value.trim()); + if (!result.isValid) { + setValidationError(result.error || ""); return false; } - - // Warning for non-Gmail (but still allow) - if (!domain.includes('gmail') && !domain.includes('googlemail')) { - setValidationError('⚠️ Works best with Gmail addresses'); + if (result.warning) { + setValidationError(result.warning); } - return true; }; + /** Appends @gmail.com when the value has no domain part. */ + const completeDomain = (value: string) => + value.includes("@") ? value : `${value}@gmail.com`; + + /** Validates the email, creates the first account, and migrates any legacy data. */ const handleSubmit = async () => { - if (!validateEmail(email.trim())) { + const emailTrimmed = completeDomain(email.trim()); + if (emailTrimmed !== email) setEmail(emailTrimmed); + + if (!validateEmail(emailTrimmed)) { return; } - + setIsSubmitting(true); - - const emailTrimmed = email.trim(); - + // Create first account const account = { id: Date.now().toString(), email: emailTrimmed, - label: 'Primary', + label: "Primary", isActive: true, }; - - // Helper to get account-specific storage key - const getAccountStorageKey = (email: string, suffix: string) => { - const sanitized = email.replace(/[^a-zA-Z0-9]/g, '_'); - return `${suffix}_${sanitized}`; - }; - + // Initialize account-specific storage - const historyKey = getAccountStorageKey(emailTrimmed, 'gmail_alias_recent'); - const statsKey = getAccountStorageKey(emailTrimmed, 'alias_stats'); - const favoritesKey = getAccountStorageKey(emailTrimmed, 'favorites'); - + const historyKey = getAccountStorageKey(emailTrimmed, "gmail_alias_recent"); + const statsKey = getAccountStorageKey(emailTrimmed, "alias_stats"); + const favoritesKey = getAccountStorageKey(emailTrimmed, "favorites"); + // Check if there's existing data in old format and migrate it - const existingData = await browser.storage.local.get(['gmail_alias_recent', 'alias_stats', 'favorites']); - - await browser.storage.local.set({ + const existingData = await browser.storage.local.get([ + "gmail_alias_recent", + "alias_stats", + "favorites", + ]); + + await browser.storage.local.set({ email_accounts: [account], base_email: emailTrimmed, // Initialize account-specific storage @@ -88,150 +101,303 @@ export default function WelcomeScreen({ onEmailAdded, onOpenSettings }: WelcomeS [statsKey]: existingData.alias_stats || { total: 0, tags: {} }, [favoritesKey]: existingData.favorites || [], }); - + onEmailAdded(emailTrimmed); setIsSubmitting(false); }; + /** Submits the form when Enter is pressed. */ const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { + if (e.key === "Enter") { handleSubmit(); - } else if (e.key === 'Tab' && email && !email.includes('@')) { - e.preventDefault(); - setEmail(email + '@gmail.com'); } }; + // Auto-complete @gmail.com on blur (Tab away included) instead of + // hijacking the Tab key, which would break normal focus navigation. + const handleBlur = () => { + if (!email) return; + const finalEmail = completeDomain(email); + if (finalEmail !== email) setEmail(finalEmail); + validateEmail(finalEmail); + }; + + // skipcq: JS-0415 return ( -
+
- {/* Logo/Icon */} -
-
- - - -
-

- Welcome to Gmail Alias Toolkit -

-

- Generate unlimited email aliases for privacy and organization -

-
+ + + { + setEmail(value); + if (validationError) setValidationError(""); + }} + onBlur={handleBlur} + onKeyPress={handleKeyPress} + onSubmit={handleSubmit} + onOpenSettings={onOpenSettings} + focusOnMount={focusOnMount} + /> + + + +
+
+ ); +} - {/* Setup Card */} -
-

- Let's get started -

- - - -
- { - setEmail(e.target.value); - if (validationError) setValidationError(''); - }} - onBlur={() => email && validateEmail(email)} - onKeyDown={handleKeyPress} - placeholder="your.email" - className={`w-full px-4 py-3 border rounded-lg text-sm focus:outline-none focus:ring-2 transition-colors ${ - validationError && !validationError.includes('⚠️') - ? 'border-red-300 focus:ring-red-500 focus:border-red-500' - : validationError && validationError.includes('⚠️') - ? 'border-amber-300 focus:ring-amber-500 focus:border-amber-500' - : 'border-gray-300 focus:ring-blue-500 focus:border-transparent' - }`} - autoFocus - /> - {email && !email.includes('@') && !validationError && ( -
- @gmail.com -
- )} -
- - {validationError && ( -
- {validationError} -
- )} - -

- 💡 Press Tab to auto-complete @gmail.com -

- - - - -
+/** Renders the logo, title, and intro copy for the first-run screen. */ +function WelcomeHeader() { + return ( +
+ + + +
+ ); +} + +/** Renders the initial Gmail account form and its validation feedback. */ +function WelcomeForm({ + email, + validationError, + isSubmitting, + onEmailChange, + onBlur, + onKeyPress, + onSubmit, + onOpenSettings, + focusOnMount, +}: WelcomeFormProps) { + const isWarning = validationError.includes("⚠️"); + + return ( +
+

+ {t("letsGetStarted")} +

- {/* Features Preview */} -
-

- What you can do: -

-
-
- - - -
-
Private Email Generator
-
Random aliases like Apple Hide My Email
-
-
- -
- - - -
-
Custom Tags & Presets
-
Shopping, work, test, social & more
-
-
- -
- - - -
-
Gmail Advanced Tricks
-
Dot trick, googlemail switch & combos
-
-
+ + +
+ } + className={`w-full ${ + validationError && !isWarning + ? "text-destructive" + : validationError && isWarning + ? "text-accent" + : "" + }`} + ref={focusOnMount} + /> + {email && !email.includes("@") && !validationError && ( +
+ @gmail.com
-
+ )} +
- {/* Footer */} -
-

- All data is stored locally. No tracking, no server. -

+ {validationError && ( +
+ {validationError}
+ )} + + {!email.includes("@") && ( +

+ 💡 {t("pressTabForGmail", "Tab").split("Tab")[0]} + + Tab + + {t("pressTabForGmail", "Tab").split("Tab")[1]} +

+ )} + + + + +
+ ); +} + +/** Wraps the welcome form sections in the shared first-run card. */ +function WelcomeCard({ children }: WelcomeCardProps) { + return ( +
+ {children} +
+ ); +} + +/** Lists the primary features available after setup. */ +function WelcomeFeatures() { + return ( +
+

+ {t("whatYouCanDo")} +

+
+ + + + } + bgColor="bg-primary/10" + label={t("featurePrivateEmail")} + /> + + + + } + bgColor="bg-accent/10" + label={t("featureCustomTags")} + /> + + + + } + bgColor="bg-primary/10" + label={t("featureGmailTricks")} + />
); } + +interface FeatureItemProps { + icon: ReactNode; + bgColor: string; + label: string; +} + +/** Renders one compact welcome feature row. */ +function FeatureItem({ icon, bgColor, label }: FeatureItemProps) { + return ( +
+ + {icon} + + {label} +
+ ); +} + +/** Renders the welcome screen footer message. */ +function WelcomeFooter() { + return ( +

+ {t("welcomeFooter")} +

+ ); +} diff --git a/entrypoints/popup/index.html b/entrypoints/popup/index.html index cb3225d..85137f4 100644 --- a/entrypoints/popup/index.html +++ b/entrypoints/popup/index.html @@ -1,16 +1,14 @@ + + + + __MSG_extensionName__ + + - - - - Gmail Alias Toolkit - - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/entrypoints/popup/main.tsx b/entrypoints/popup/main.tsx index 4cc9737..2f4c628 100644 --- a/entrypoints/popup/main.tsx +++ b/entrypoints/popup/main.tsx @@ -1,9 +1,17 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App.tsx'; -import './style.css'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import "./style.css"; +import { t } from "../../lib/i18n"; -ReactDOM.createRoot(document.getElementById('root')!).render( +document.title = t("extensionName"); + +const rootElement = document.getElementById("root"); +if (!rootElement) { + throw new Error("Root element #root not found"); +} + +ReactDOM.createRoot(rootElement).render( , diff --git a/entrypoints/popup/style.css b/entrypoints/popup/style.css index a4c080c..c432b89 100644 --- a/entrypoints/popup/style.css +++ b/entrypoints/popup/style.css @@ -1,6 +1,86 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; + +@source "."; +@source "../../src"; + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --shadow-soft: 0 14px 35px -22px rgb(15 23 42 / 0.45); +} + +:root { + color-scheme: light; + --background: oklch(99% 0 0); + --foreground: oklch(15% 0 0); + --card: oklch(97% 0 0); + --card-foreground: oklch(15% 0 0); + --popover: oklch(97% 0 0); + --popover-foreground: oklch(15% 0 0); + --primary: oklch(55% 0.18 255); + --primary-foreground: oklch(99% 0 0); + --secondary: oklch(97% 0 0); + --secondary-foreground: oklch(15% 0 0); + --muted: oklch(97% 0 0); + --muted-foreground: oklch(50% 0 0); + --accent: oklch(55% 0.18 255); + --accent-foreground: oklch(99% 0 0); + --destructive: oklch(62% 0.22 25); + --destructive-foreground: oklch(99% 0 0); + --border: oklch(15% 0 0 / 0.06); + --input: oklch(15% 0 0 / 0.06); + --ring: oklch(55% 0.18 255 / 0.5); + --glass-bg: rgb(255 255 255 / 0.72); + --glass-strong-bg: rgb(255 255 255 / 0.86); + --glass-thin-bg: rgb(255 255 255 / 0.52); + --glass-border: rgb(15 23 42 / 0.08); +} + +.dark { + color-scheme: dark; + --background: #111318; + --foreground: oklch(96% 0.01 255); + --card: #191b21; + --card-foreground: oklch(96% 0.01 255); + --popover: #20232b; + --popover-foreground: oklch(96% 0.01 255); + --primary: oklch(64% 0.17 255); + --primary-foreground: oklch(99% 0 0); + --secondary: #20232b; + --secondary-foreground: oklch(96% 0.01 255); + --muted: #20232b; + --muted-foreground: oklch(72% 0.02 255); + --accent: oklch(70% 0.16 255); + --accent-foreground: oklch(99% 0 0); + --destructive: oklch(62% 0.22 25); + --destructive-foreground: oklch(96% 0 0); + --border: rgb(255 255 255 / 0.1); + --input: rgb(255 255 255 / 0.12); + --ring: oklch(64% 0.17 255 / 0.6); + --glass-bg: rgb(25 27 33 / 0.74); + --glass-strong-bg: rgb(25 27 33 / 0.9); + --glass-thin-bg: rgb(32 35 43 / 0.58); + --glass-border: rgb(255 255 255 / 0.12); +} * { margin: 0; @@ -9,16 +89,404 @@ } body { - width: 360px; - min-height: 480px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + width: 380px; + height: 600px; + overflow: hidden; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", + "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #root { width: 100%; - min-height: 100%; + height: 100%; +} + +@utility glass { + background: var(--glass-bg); + backdrop-filter: blur(16px); + border: 1px solid var(--glass-border); +} + +@utility glass-strong { + background: var(--glass-strong-bg); + backdrop-filter: blur(16px); +} + +@utility glass-thin { + background: var(--glass-thin-bg); + backdrop-filter: blur(12px); + border: 1px solid var(--glass-border); +} + +/* + * Popup runtime compatibility for Tailwind v4 spacing utilities. + * Some extension runtimes do not apply `calc(var(--spacing) * n)` reliably, + * which collapses padding, gaps, widths, and icon sizes. These static fallbacks + * intentionally cover the spacing scale used by the popup UI. + */ +.inset-0 { + inset: 0; +} +.top-0 { + top: 0; +} +.top-1\/2 { + top: 50%; +} +.top-4 { + top: 1rem; +} +.right-0 { + right: 0; +} +.right-2 { + right: 0.5rem; +} +.right-3 { + right: 0.75rem; +} +.right-3\.5 { + right: 0.875rem; +} +.right-4 { + right: 1rem; +} +.bottom-4 { + bottom: 1rem; +} +.bottom-6 { + bottom: 1.5rem; +} +.bottom-16 { + bottom: 4rem; +} +.left-0 { + left: 0; +} +.left-1\/2 { + left: 50%; +} +.left-3 { + left: 0.75rem; +} +.left-3\.5 { + left: 0.875rem; +} +.left-4 { + left: 1rem; +} + +.m-0 { + margin: 0; +} +.mx-auto { + margin-inline: auto; +} +.mx-4 { + margin-inline: 1rem; +} +.-mt-1 { + margin-top: -0.25rem; +} +.mt-0\.5 { + margin-top: 0.125rem; +} +.mt-1 { + margin-top: 0.25rem; +} +.mt-2 { + margin-top: 0.5rem; +} +.mt-3 { + margin-top: 0.75rem; +} +.mt-4 { + margin-top: 1rem; +} +.mr-1 { + margin-right: 0.25rem; +} +.mr-1\.5 { + margin-right: 0.375rem; +} +.mr-2 { + margin-right: 0.5rem; +} +.mb-0\.5 { + margin-bottom: 0.125rem; +} +.mb-1 { + margin-bottom: 0.25rem; +} +.mb-1\.5 { + margin-bottom: 0.375rem; +} +.mb-2 { + margin-bottom: 0.5rem; +} +.mb-2\.5 { + margin-bottom: 0.625rem; +} +.mb-3 { + margin-bottom: 0.75rem; +} +.ml-1 { + margin-left: 0.25rem; +} +.ml-1\.5 { + margin-left: 0.375rem; +} +.ml-2 { + margin-left: 0.5rem; +} +.ml-auto { + margin-left: auto; +} + +.p-1 { + padding: 0.25rem; +} +.p-1\.5 { + padding: 0.375rem; +} +.p-2 { + padding: 0.5rem; +} +.p-2\.5 { + padding: 0.625rem; +} +.p-3 { + padding: 0.75rem; +} +.p-3\.5 { + padding: 0.875rem; +} +.p-4 { + padding: 1rem; +} +.p-5 { + padding: 1.25rem; +} +.p-6 { + padding: 1.5rem; +} +.px-1 { + padding-inline: 0.25rem; +} +.px-1\.5 { + padding-inline: 0.375rem; +} +.px-2 { + padding-inline: 0.5rem; +} +.px-2\.5 { + padding-inline: 0.625rem; +} +.px-3 { + padding-inline: 0.75rem; +} +.px-4 { + padding-inline: 1rem; +} +.px-5 { + padding-inline: 1.25rem; +} +.px-6 { + padding-inline: 1.5rem; +} +.py-0\.5 { + padding-block: 0.125rem; +} +.py-1 { + padding-block: 0.25rem; +} +.py-1\.5 { + padding-block: 0.375rem; +} +.py-2 { + padding-block: 0.5rem; +} +.py-2\.5 { + padding-block: 0.625rem; +} +.py-3 { + padding-block: 0.75rem; +} +.pt-2 { + padding-top: 0.5rem; +} +.pt-3 { + padding-top: 0.75rem; +} +.pt-4 { + padding-top: 1rem; +} +.pr-2 { + padding-right: 0.5rem; +} +.pr-3 { + padding-right: 0.75rem; +} +.pr-4 { + padding-right: 1rem; +} +.pb-2 { + padding-bottom: 0.5rem; +} +.pb-3 { + padding-bottom: 0.75rem; +} +.pb-4 { + padding-bottom: 1rem; +} +.pl-2 { + padding-left: 0.5rem; +} +.pl-3 { + padding-left: 0.75rem; +} +.pl-4 { + padding-left: 1rem; +} +.pl-10 { + padding-left: 2.5rem; +} + +.gap-0 { + gap: 0; +} +.gap-0\.5 { + gap: 0.125rem; +} +.gap-1 { + gap: 0.25rem; +} +.gap-1\.5 { + gap: 0.375rem; +} +.gap-2 { + gap: 0.5rem; +} +.gap-2\.5 { + gap: 0.625rem; +} +.gap-3 { + gap: 0.75rem; +} +.gap-4 { + gap: 1rem; +} +.space-y-0\.5 > :not(:last-child) { + margin-block-end: 0.125rem; +} +.space-y-1\.5 > :not(:last-child) { + margin-block-end: 0.375rem; +} +.space-y-2 > :not(:last-child) { + margin-block-end: 0.5rem; +} +.space-y-3 > :not(:last-child) { + margin-block-end: 0.75rem; +} +.space-y-4 > :not(:last-child) { + margin-block-end: 1rem; +} + +.size-5 { + width: 1.25rem; + height: 1.25rem; +} +.h-3 { + height: 0.75rem; +} +.h-3\.5 { + height: 0.875rem; +} +.h-4 { + height: 1rem; +} +.h-5 { + height: 1.25rem; +} +.h-6 { + height: 1.5rem; +} +.h-7 { + height: 1.75rem; +} +.h-8 { + height: 2rem; +} +.h-9 { + height: 2.25rem; +} +.h-10 { + height: 2.5rem; +} +.h-11 { + height: 2.75rem; +} +.h-12 { + height: 3rem; +} +.h-full { + height: 100%; +} +.h-px { + height: 1px; +} +.min-h-9 { + min-height: 2.25rem; +} +.min-h-10 { + min-height: 2.5rem; +} +.w-3 { + width: 0.75rem; +} +.w-3\.5 { + width: 0.875rem; +} +.w-4 { + width: 1rem; +} +.w-5 { + width: 1.25rem; +} +.w-6 { + width: 1.5rem; +} +.w-7 { + width: 1.75rem; +} +.w-8 { + width: 2rem; +} +.w-10 { + width: 2.5rem; +} +.w-11 { + width: 2.75rem; +} +.w-12 { + width: 3rem; +} +.w-20 { + width: 5rem; +} +.w-full { + width: 100%; +} +.w-px { + width: 1px; +} + +.\[\&_svg\]\:h-4 svg { + height: 1rem; +} +.\[\&_svg\]\:w-4 svg { + width: 1rem; } diff --git a/entrypoints/popup/utils.ts b/entrypoints/popup/utils.ts new file mode 100644 index 0000000..c3883c6 --- /dev/null +++ b/entrypoints/popup/utils.ts @@ -0,0 +1,243 @@ +/** Builds a collision-free, case-insensitive storage key for account-scoped data. */ +export function getAccountStorageKey(email: string, suffix: string): string { + const normalized = encodeURIComponent(email.trim().toLowerCase()); + return `${suffix}_${normalized}`; +} + +/** Pre-fix (lossy, non-injective) key format kept only for one-time migration. */ +export function getLegacyAccountStorageKey( + email: string, + suffix: string, +): string { + const sanitized = email.replace(/[^a-zA-Z0-9]/g, "_"); + return `${suffix}_${sanitized}`; +} + +/** Creates a plus-addressed alias (user+tag@domain), or null if the base email is malformed. */ +export function generateAlias(baseEmail: string, tag: string): string | null { + const parts = baseEmail.trim().split("@"); + if (parts.length !== 2) return null; + + const [username, domain] = parts; + if (!username || !domain) return null; + return `${username}+${tag}@${domain}`; +} + +export type RandomFormat = + | "private-mail" + | "alphanumeric" + | "words" + | "timestamp"; + +/** Generates a random alias tag in the given format; `index` de-duplicates timestamp batches. */ +export function generateRandomString(format: RandomFormat, index = 0): string { + if (format === "private-mail") { + const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < 4; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return `private-mail-${result}`; + } + + if (format === "timestamp") { + return (Date.now() + index).toString(36); + } + + if (format === "words") { + const adjectives = [ + "happy", + "sunny", + "calm", + "bright", + "swift", + "brave", + "cool", + "smart", + "quick", + "zen", + "wild", + "free", + "bold", + "wise", + "pure", + "kind", + "fair", + "true", + "rare", + "fine", + ]; + const nouns = [ + "fox", + "bird", + "bear", + "wolf", + "deer", + "lion", + "hawk", + "eagle", + "tiger", + "panda", + "seal", + "otter", + "raven", + "crane", + "swan", + "lynx", + "coral", + "pearl", + "jade", + "ruby", + ]; + const adj = adjectives[Math.floor(Math.random() * adjectives.length)]; + const noun = nouns[Math.floor(Math.random() * nouns.length)]; + const num = Math.floor(Math.random() * 999); + return `${adj}-${noun}-${num}`; + } + + // alphanumeric + const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < 8; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +type ValidationResult = { + isValid: boolean; + error?: string; + warning?: string; +}; + +/** Validates an email address; non-Gmail domains pass with a warning. */ +export function validateEmail(value: string): ValidationResult { + const email = value.trim(); + if (!email) return { isValid: false, error: "Email is required" }; + if (!email.includes("@")) + return { isValid: false, error: "Please enter a valid email address" }; + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) + return { isValid: false, error: "Invalid email format" }; + + const [username, domain] = email.split("@"); + if (username.length < 1) + return { isValid: false, error: "Username cannot be empty" }; + if (!domain.includes(".")) + return { + isValid: false, + error: "Domain must include a dot (e.g., gmail.com)", + }; + + const normalizedDomain = domain.toLowerCase(); + const isGmail = + normalizedDomain === "gmail.com" || normalizedDomain === "googlemail.com"; + + if (!isGmail) { + return { isValid: true, warning: "⚠️ Works best with Gmail addresses" }; + } + + return { isValid: true }; +} + +/** Generates Gmail dot-placement variations of a username, exhaustive or randomized. */ +export function generateDotVariations( + username: string, + count = 10, + randomize = false, +): string[] { + if (username.length < 2) return []; + + const variations: string[] = []; + + if (randomize) { + for (let i = 0; i < count; i++) { + const chars = username.split(""); + const maxDots = Math.min(3, chars.length - 1); + const numDots = Math.floor(Math.random() * maxDots) + 1; + const positions = new Set(); + + while (positions.size < numDots) { + const pos = Math.floor(Math.random() * (chars.length - 1)) + 1; + positions.add(pos); + } + + const sortedPositions = Array.from(positions).sort((a, b) => a - b); + let result = ""; + let lastPos = 0; + sortedPositions.forEach((pos) => { + result += `${chars.slice(lastPos, pos).join("")}.`; + lastPos = pos; + }); + result += chars.slice(lastPos).join(""); + variations.push(result); + } + } else { + const len = username.length; + for (let i = 1; i < len; i++) { + variations.push(`${username.slice(0, i)}.${username.slice(i)}`); + } + + if (username.length >= 4) { + for (let i = 1; i < len - 1; i++) { + for (let j = i + 1; j < len; j++) { + variations.push( + `${username.slice(0, i)}.${username.slice(i, j)}.${username.slice(j)}`, + ); + } + } + } + } + + return [...new Set(variations)].slice(0, count); +} + +/** Returns dot variations, or the original username when dots cannot be inserted. */ +export function getDotVariationCandidates( + username: string, + count = 10, + randomize = false, +): string[] { + const variations = generateDotVariations(username, count, randomize); + return variations.length > 0 ? variations : [username]; +} + +/** Filters and sorts aliases by view mode, search query, tag, and sort order. */ +export function filterAliases( + aliases: Array<{ email: string; timestamp: number }>, + opts: { + viewMode: "all" | "favorites"; + favorites: string[]; + searchQuery: string; + filterTag: string; + sortBy: "recent" | "alphabetical"; + }, +): Array<{ email: string; timestamp: number }> { + return aliases + .filter((alias) => { + if ( + opts.viewMode === "favorites" && + !opts.favorites.includes(alias.email) + ) + return false; + if ( + opts.searchQuery && + !alias.email + .toLowerCase() + .includes(opts.searchQuery.trim().toLowerCase()) + ) + return false; + if (opts.filterTag !== "all") { + const tagMatch = alias.email.match(/\+([^@]+)@/); + const emailTag = tagMatch ? tagMatch[1] : null; + if (emailTag !== opts.filterTag) return false; + } + return true; + }) + .sort((a, b) => + opts.sortBy === "recent" + ? b.timestamp - a.timestamp + : a.email.localeCompare(b.email), + ); +} diff --git a/lib/i18n.ts b/lib/i18n.ts new file mode 100644 index 0000000..d7bbcea --- /dev/null +++ b/lib/i18n.ts @@ -0,0 +1,13 @@ +type MessageSubstitution = string | string[]; + +/** Returns a localized extension message, falling back to the message key. */ +export function t(messageName: string, substitutions?: MessageSubstitution) { + try { + return ( + browser.i18n.getMessage(messageName as never, substitutions) || + messageName + ); + } catch { + return messageName; + } +} diff --git a/package.json b/package.json index d079054..cd7780d 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,15 @@ "name": "gmail-alias-toolkit", "description": "Generate and manage Gmail aliases with plus addressing and presets", "private": true, - "version": "1.1.0", + "version": "1.2.0", "author": "dev@eplus.dev", "license": "MIT", "homepage_url": "https://eplus.dev", "type": "module", + "packageManager": "yarn@4.14.1", + "engines": { + "node": ">=24.0.0" + }, "scripts": { "dev": "wxt", "dev:firefox": "wxt -b firefox", @@ -15,21 +19,40 @@ "zip": "wxt zip", "zip:firefox": "wxt zip -b firefox", "compile": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", "postinstall": "wxt prepare" }, "dependencies": { + "@tanstack/react-virtual": "^3.14.5", + "@wxt-dev/auto-icons": "^1.1.0", + "@wxt-dev/module-react": "^1.1.5", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "motion": "^12.0.0", + "next-themes": "^0.4.6", + "qrcode": "^1.5.4", "react": "^19.2.3", - "react-dom": "^19.2.3" + "react-dom": "^19.2.3", + "tailwind-merge": "^2.6.0" }, "devDependencies": { + "@tailwindcss/postcss": "^4.3.2", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", - "@wxt-dev/auto-icons": "^1.1.0", - "@wxt-dev/module-react": "^1.1.5", + "@vitejs/plugin-react": "^6.0.3", "autoprefixer": "^10.4.20", + "jsdom": "^29.1.1", "postcss": "^8.5.10", - "tailwindcss": "^3.4.17", + "tailwindcss": "^4", "typescript": "^5.9.3", - "wxt": "^0.20.6" + "vite": "^8.1.1", + "vitest": "^4.1.9", + "wxt": "^0.20.27" } } diff --git a/postcss.config.js b/postcss.config.js index 2aa7205..c2ddf74 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,5 @@ export default { plugins: { - tailwindcss: {}, - autoprefixer: {}, + "@tailwindcss/postcss": {}, }, }; diff --git a/public/_locales/de/messages.json b/public/_locales/de/messages.json new file mode 100644 index 0000000..3150b95 --- /dev/null +++ b/public/_locales/de/messages.json @@ -0,0 +1,697 @@ +{ + "extensionName": { + "message": "Gmail Alias Toolkit" + }, + "extensionDescription": { + "message": "Gmail-Aliase mit Plus-Adressierung und Vorlagen erstellen und verwalten" + }, + "headerSubtitle": { + "message": "Aliase mit Plus-Adressierung erstellen" + }, + "settings": { + "message": "Einstellungen" + }, + "back": { + "message": "Zurück" + }, + "close": { + "message": "Schließen" + }, + "cancel": { + "message": "Abbrechen" + }, + "copy": { + "message": "Kopieren" + }, + "copyAll": { + "message": "Alle kopieren" + }, + "copyToClipboard": { + "message": "In die Zwischenablage kopieren" + }, + "activeGmailAddress": { + "message": "Aktive Gmail-Adresse" + }, + "addNewAccount": { + "message": "Neues Konto hinzufügen" + }, + "addAccount": { + "message": "Konto hinzufügen" + }, + "addNewAccountTitle": { + "message": "Neues Konto hinzufügen" + }, + "emailPlaceholder": { + "message": "deine.email" + }, + "accountLabelPlaceholder": { + "message": "Bezeichnung (optional, z. B. Arbeit, Privat)" + }, + "pressTabToAddGmail": { + "message": "Drücke $KEY$, um @gmail.com hinzuzufügen", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "gmailWarning": { + "message": "Das sieht nicht wie eine Gmail-Adresse aus. Plus-Adressierung funktioniert am besten mit Gmail." + }, + "random": { + "message": "Zufällig" + }, + "customTags": { + "message": "Benutzerdefinierte Tags" + }, + "gmailTricks": { + "message": "Gmail-Tricks" + }, + "tabTagsShort": { + "message": "Tags" + }, + "tabTricksShort": { + "message": "Tricks" + }, + "generating": { + "message": "Wird generiert" + }, + "copied": { + "message": "Kopiert" + }, + "switchToLightMode": { + "message": "Zum hellen Modus wechseln" + }, + "switchToDarkMode": { + "message": "Zum dunklen Modus wechseln" + }, + "aliasColumn": { + "message": "Alias" + }, + "copyEmailTooltip": { + "message": "$EMAIL$ - klicken zum Kopieren", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "pageLabel": { + "message": "Seite $PAGE$", + "placeholders": { + "page": { + "content": "$1" + } + } + }, + "dotTrick": { + "message": "Punkt-Trick" + }, + "plusTags": { + "message": "Plus-Tags (+)" + }, + "googlemail": { + "message": "Googlemail" + }, + "removeDots": { + "message": "Punkte entfernen" + }, + "dotPlus": { + "message": "Punkt + Plus" + }, + "allCombos": { + "message": "Alle Kombinationen" + }, + "numberOfVariations": { + "message": "Anzahl der Varianten" + }, + "randomizeDotPositions": { + "message": "Punktpositionen zufällig wählen" + }, + "sequential": { + "message": "Nacheinander" + }, + "generateTricks": { + "message": "Tricks generieren" + }, + "generatedVariations": { + "message": "Generierte Varianten" + }, + "gmailTrickInfoLabel": { + "message": "Gmail-Trick:" + }, + "gmailTrickInfo": { + "message": "Punkte werden ignoriert und alles nach + landet im selben Posteingang" + }, + "format": { + "message": "Format" + }, + "privateMailFormat": { + "message": "Private Mail (private-mail-xxxx)" + }, + "randomCharactersFormat": { + "message": "Zufällige Zeichen (abc123xy)" + }, + "randomWordsFormat": { + "message": "Zufällige Wörter (happy-fox-42)" + }, + "timestampFormat": { + "message": "Zeitstempel (1234567890)" + }, + "numberOfAliases": { + "message": "Anzahl der Aliase" + }, + "generateRandomAliases": { + "message": "$COUNT$ zufällige Aliase generieren", + "placeholders": { + "count": { + "content": "$1" + }, + "plural": { + "content": "$2" + } + } + }, + "generatedAliases": { + "message": "Generierte Aliase" + }, + "totalCount": { + "message": "$COUNT$ insgesamt", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "copiedAliases": { + "message": "$COUNT$ Aliase kopiert!", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "failedToCopy": { + "message": "Kopieren fehlgeschlagen" + }, + "formatPrivateMail": { + "message": "Format: private-mail-xxxx" + }, + "formatAlphanumeric": { + "message": "8 zufällige Zeichen" + }, + "formatWords": { + "message": "2 zufällige Wörter" + }, + "formatTimestamp": { + "message": "Unix-Zeitstempel" + }, + "tagPlaceholder": { + "message": "Tag eingeben (z. B. shopping, arbeit)" + }, + "generate": { + "message": "Generieren" + }, + "yourPresets": { + "message": "Deine Vorlagen" + }, + "example": { + "message": "Beispiel:" + }, + "copyExample": { + "message": "Beispiel kopieren" + }, + "recentAliases": { + "message": "Aktuelle Aliase" + }, + "favorites": { + "message": "Favoriten" + }, + "exportAsCsv": { + "message": "Als CSV exportieren" + }, + "exportAsJson": { + "message": "Als JSON exportieren" + }, + "selectAliases": { + "message": "Aliase auswählen" + }, + "select": { + "message": "Auswählen" + }, + "starredCount": { + "message": "$COUNT$ markiert", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deselectAll": { + "message": "Auswahl aufheben" + }, + "selectAll": { + "message": "Alle auswählen" + }, + "selectedCount": { + "message": "$COUNT$ ausgewählt", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deleteCount": { + "message": "$COUNT$ löschen", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "allCount": { + "message": "Alle ($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "favoritesCount": { + "message": "Favoriten ($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "searchAliases": { + "message": "Aliase suchen..." + }, + "allTags": { + "message": "Alle Tags" + }, + "mostRecent": { + "message": "Neueste" + }, + "az": { + "message": "A-Z" + }, + "noFavoritesYet": { + "message": "Noch keine Favoriten" + }, + "starEmailsHint": { + "message": "Markiere E-Mails aus deinem Verlauf, um hier schnell darauf zuzugreifen" + }, + "noResultsFound": { + "message": "Keine Ergebnisse gefunden" + }, + "differentSearchHint": { + "message": "Versuche eine andere Suche oder einen anderen Filter" + }, + "showQrCode": { + "message": "QR-Code anzeigen" + }, + "removeFromFavorites": { + "message": "Aus Favoriten entfernen" + }, + "addToFavorites": { + "message": "Zu Favoriten hinzufügen" + }, + "showingRange": { + "message": "$START$–$END$ von $TOTAL$ werden angezeigt", + "placeholders": { + "start": { + "content": "$1" + }, + "end": { + "content": "$2" + }, + "total": { + "content": "$3" + } + } + }, + "perPage": { + "message": "$COUNT$ / Seite", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "firstPage": { + "message": "Erste Seite" + }, + "previousPage": { + "message": "Vorherige Seite" + }, + "nextPage": { + "message": "Nächste Seite" + }, + "lastPage": { + "message": "Letzte Seite" + }, + "scanToCopyAlias": { + "message": "Scannen, um Alias zu kopieren" + }, + "general": { + "message": "Allgemein" + }, + "accounts": { + "message": "Konten" + }, + "changelog": { + "message": "Änderungsprotokoll" + }, + "appearanceDisplay": { + "message": "Darstellung & Anzeige" + }, + "theme": { + "message": "Design" + }, + "themeLight": { + "message": "Hell" + }, + "themeDark": { + "message": "Dunkel" + }, + "themeAuto": { + "message": "System (Automatisch)" + }, + "badgeCounter": { + "message": "Badge-Zähler" + }, + "badgeNone": { + "message": "Keine (Ausgeblendet)" + }, + "badgeTotal": { + "message": "Gesamt im Verlauf" + }, + "badgeAllTime": { + "message": "Insgesamt generiert" + }, + "badgeToday": { + "message": "Heute erstellt" + }, + "badgeWeek": { + "message": "Diese Woche" + }, + "copyNotifications": { + "message": "Kopierbenachrichtigungen" + }, + "aliasGeneration": { + "message": "Alias-Generierung" + }, + "randomAliasFormat": { + "message": "Format für zufällige Aliase" + }, + "autoSaveLimit": { + "message": "Limit für automatisches Speichern" + }, + "aliasesLimit": { + "message": "$COUNT$ Aliase", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "customPresets": { + "message": "Benutzerdefinierte Vorlagen" + }, + "label": { + "message": "Bezeichnung" + }, + "tag": { + "message": "Tag" + }, + "addPreset": { + "message": "+ Vorlage hinzufügen" + }, + "dataManagement": { + "message": "Datenverwaltung" + }, + "export": { + "message": "Exportieren" + }, + "import": { + "message": "Importieren" + }, + "clear": { + "message": "Leeren" + }, + "resetSettings": { + "message": "Alle Einstellungen auf Standard zurücksetzen" + }, + "resetSettingsTitle": { + "message": "Einstellungen zurücksetzen?" + }, + "resetSettingsMessage": { + "message": "Dadurch werden alle Einstellungen auf ihre Standardwerte zurückgesetzt." + }, + "reset": { + "message": "Zurücksetzen" + }, + "clearHistoryTitle": { + "message": "Aktuelle Aliase löschen?" + }, + "clearHistoryMessage": { + "message": "Dadurch werden alle Aliase aus der Liste des aktuellen Verlaufs entfernt." + }, + "emailAccounts": { + "message": "E-Mail-Konten" + }, + "manageAccountsDescription": { + "message": "Verwalte deine Gmail-Konten. Jedes Konto hat seinen eigenen Verlauf, eigene Statistiken und Favoriten." + }, + "noAccountsFound": { + "message": "Keine Konten gefunden. Bitte füge ein Konto über den Hauptbildschirm hinzu." + }, + "emailAddress": { + "message": "E-Mail-Adresse" + }, + "accountLabel": { + "message": "Kontobezeichnung" + }, + "emailAddressPlaceholder": { + "message": "deine.email@gmail.com" + }, + "emailChangeWarning": { + "message": "Beim Ändern der E-Mail werden alle Daten zur neuen E-Mail-Adresse migriert" + }, + "saveChanges": { + "message": "Änderungen speichern" + }, + "active": { + "message": "Aktiv" + }, + "editAccount": { + "message": "Konto bearbeiten" + }, + "cannotDeleteLastAccountTitle": { + "message": "Das letzte Konto kann nicht gelöscht werden" + }, + "deleteThisAccount": { + "message": "Dieses Konto löschen" + }, + "deleteAccountTitle": { + "message": "Konto löschen?" + }, + "deleteAccountMessage": { + "message": "„$LABEL$“ ($EMAIL$) löschen?\n\nDies löscht dauerhaft:\n- Den gesamten Verlauf dieses Kontos\n- Alle Statistiken\n- Alle Favoriten\n\nDiese Aktion kann nicht rückgängig gemacht werden.", + "placeholders": { + "label": { + "content": "$1" + }, + "email": { + "content": "$2" + } + } + }, + "delete": { + "message": "Löschen" + }, + "changeAccountEmailTitle": { + "message": "Konto-E-Mail ändern?" + }, + "changeAccountEmailMessage": { + "message": "E-Mail ändern von\n$OLD_EMAIL$\nzu\n$NEW_EMAIL$?\n\nDies wird:\n- Den gesamten Verlauf, alle Statistiken und Favoriten zur neuen E-Mail migrieren\n- Die Konto-E-Mail aktualisieren\n- Mit der alten E-Mail verknüpfte Daten löschen\n\nFortfahren?", + "placeholders": { + "old_email": { + "content": "$1" + }, + "new_email": { + "content": "$2" + } + } + }, + "changeEmail": { + "message": "E-Mail ändern" + }, + "menuRandomEmailAlias": { + "message": "Zufälliger E-Mail-Alias" + }, + "menuCustomTags": { + "message": "Benutzerdefinierte Tags" + }, + "menuNoPresets": { + "message": "Keine Vorlagen – in den Einstellungen hinzufügen" + }, + "menuGmailTricks": { + "message": "Gmail-Tricks" + }, + "menuDotVariation": { + "message": "Punkt-Variation" + }, + "menuGooglemailDomain": { + "message": "Googlemail-Domain" + }, + "menuRemoveAllDots": { + "message": "Alle Punkte entfernen" + }, + "toastPresetAdded": { + "message": "Vorlage hinzugefügt" + }, + "toastPresetRemoved": { + "message": "Vorlage entfernt" + }, + "toastSettingsExported": { + "message": "Einstellungen exportiert" + }, + "toastSettingsImported": { + "message": "Einstellungen importiert" + }, + "toastImportFailed": { + "message": "Import fehlgeschlagen – ungültige Datei" + }, + "toastSettingsReset": { + "message": "Einstellungen auf Standard zurückgesetzt" + }, + "toastAccountSwitched": { + "message": "Konto gewechselt" + }, + "toastAccountDeleted": { + "message": "Konto gelöscht" + }, + "toastAccountUpdated": { + "message": "Konto aktualisiert" + }, + "toastAccountAdded": { + "message": "$LABEL$ hinzugefügt", + "placeholders": { + "label": { + "content": "$1" + } + } + }, + "errorLabelRequired": { + "message": "Bezeichnung darf nicht leer sein" + }, + "errorInvalidEmail": { + "message": "Bitte gib eine gültige E-Mail-Adresse ein" + }, + "errorDuplicateEmail": { + "message": "Diese E-Mail-Adresse wird bereits von einem anderen Konto verwendet" + }, + "errorEnterEmail": { + "message": "Bitte gib eine E-Mail-Adresse ein" + }, + "errorAccountExists": { + "message": "Dieses Konto existiert bereits" + }, + "toastQrFailed": { + "message": "QR-Code konnte nicht erstellt werden" + }, + "toastExportedAliases": { + "message": "$COUNT$ Aliase exportiert", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastDeletedAliases": { + "message": "$COUNT$ Aliase gelöscht", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastHistoryCleared": { + "message": "Verlauf gelöscht" + }, + "toastFavoriteAdded": { + "message": "Zu Favoriten hinzugefügt" + }, + "toastFavoriteRemoved": { + "message": "Aus Favoriten entfernt" + }, + "toastCopiedEmail": { + "message": "$EMAIL$ kopiert", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "toastCopyFailed": { + "message": "Kopieren fehlgeschlagen" + }, + "welcomeTitle": { + "message": "Willkommen bei Gmail Alias Toolkit" + }, + "welcomeSubtitle": { + "message": "Erstelle unbegrenzt E-Mail-Aliase für Datenschutz und Organisation" + }, + "letsGetStarted": { + "message": "Los geht’s" + }, + "enterGmailAddress": { + "message": "Gib deine Gmail-Adresse ein" + }, + "pressTabForGmail": { + "message": "Drücke $KEY$ für @gmail.com", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "settingUp": { + "message": "Einrichtung läuft..." + }, + "getStarted": { + "message": "Starten" + }, + "advancedSetup": { + "message": "Erweiterte Einrichtung in den Einstellungen" + }, + "whatYouCanDo": { + "message": "Was du tun kannst:" + }, + "featurePrivateEmail": { + "message": "Private-E-Mail-Generator" + }, + "featureCustomTags": { + "message": "Benutzerdefinierte Tags & Vorlagen" + }, + "featureGmailTricks": { + "message": "Erweiterte Gmail-Tricks" + }, + "welcomeFooter": { + "message": "Alle Daten werden lokal gespeichert. Kein Tracking, kein Server." + }, + "favoritesSaved": { + "message": "$COUNT$ gespeichert", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "clickStarHint": { + "message": "Klicke bei einem Alias im Verlauf auf den Stern, um ihn hier hinzuzufügen" + } +} diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json new file mode 100644 index 0000000..940341f --- /dev/null +++ b/public/_locales/en/messages.json @@ -0,0 +1,301 @@ +{ + "extensionName": { "message": "Gmail Alias Toolkit" }, + "extensionDescription": { + "message": "Generate and manage Gmail aliases with plus addressing and presets" + }, + "headerSubtitle": { "message": "Generate aliases with plus addressing" }, + "settings": { "message": "Settings" }, + "back": { "message": "Back" }, + "close": { "message": "Close" }, + "cancel": { "message": "Cancel" }, + "copy": { "message": "Copy" }, + "copyAll": { "message": "Copy All" }, + "copyToClipboard": { "message": "Copy to clipboard" }, + "activeGmailAddress": { "message": "Active Gmail Address" }, + "addNewAccount": { "message": "Add New Account" }, + "addAccount": { "message": "Add Account" }, + "addNewAccountTitle": { "message": "Add New Account" }, + "emailPlaceholder": { "message": "your.email" }, + "accountLabelPlaceholder": { + "message": "Label (optional, e.g., Work, Personal)" + }, + "pressTabToAddGmail": { + "message": "Press $KEY$ to add @gmail.com", + "placeholders": { "key": { "content": "$1" } } + }, + "gmailWarning": { + "message": "This doesn't look like a Gmail address. Plus addressing works best with Gmail." + }, + "random": { "message": "Random" }, + "customTags": { "message": "Custom Tags" }, + "gmailTricks": { "message": "Gmail Tricks" }, + "tabTagsShort": { "message": "Tags" }, + "tabTricksShort": { "message": "Tricks" }, + "generating": { "message": "Generating" }, + "copied": { "message": "Copied" }, + "switchToLightMode": { "message": "Switch to light mode" }, + "switchToDarkMode": { "message": "Switch to dark mode" }, + "aliasColumn": { "message": "Alias" }, + "copyEmailTooltip": { + "message": "$EMAIL$ - click to copy", + "placeholders": { "email": { "content": "$1" } } + }, + "pageLabel": { + "message": "Page $PAGE$", + "placeholders": { "page": { "content": "$1" } } + }, + "dotTrick": { "message": "Dot Trick" }, + "plusTags": { "message": "Plus (+) Tags" }, + "googlemail": { "message": "Googlemail" }, + "removeDots": { "message": "Remove Dots" }, + "dotPlus": { "message": "Dot + Plus" }, + "allCombos": { "message": "All Combos" }, + "numberOfVariations": { "message": "Number of variations" }, + "randomizeDotPositions": { "message": "Randomize dot positions" }, + "sequential": { "message": "Sequential" }, + "generateTricks": { "message": "Generate Tricks" }, + "generatedVariations": { "message": "Generated Variations" }, + "gmailTrickInfoLabel": { "message": "Gmail trick:" }, + "gmailTrickInfo": { + "message": "Dots are ignored and everything after + goes to the same inbox" + }, + "format": { "message": "Format" }, + "privateMailFormat": { "message": "Private Mail (private-mail-xxxx)" }, + "randomCharactersFormat": { "message": "Random Characters (abc123xy)" }, + "randomWordsFormat": { "message": "Random Words (happy-fox-42)" }, + "timestampFormat": { "message": "Timestamp (1234567890)" }, + "numberOfAliases": { "message": "Number of aliases" }, + "generateRandomAliases": { + "message": "Generate $COUNT$ Random Alias$PLURAL$", + "placeholders": { + "count": { "content": "$1" }, + "plural": { "content": "$2" } + } + }, + "generatedAliases": { "message": "Generated Aliases" }, + "totalCount": { + "message": "$COUNT$ total", + "placeholders": { "count": { "content": "$1" } } + }, + "copiedAliases": { + "message": "Copied $COUNT$ aliases!", + "placeholders": { "count": { "content": "$1" } } + }, + "failedToCopy": { "message": "Failed to copy" }, + "formatPrivateMail": { "message": "Format: private-mail-xxxx" }, + "formatAlphanumeric": { "message": "8 random characters" }, + "formatWords": { "message": "2 random words" }, + "formatTimestamp": { "message": "Unix timestamp" }, + "tagPlaceholder": { "message": "Enter tag (e.g., shopping, work)" }, + "generate": { "message": "Generate" }, + "yourPresets": { "message": "Your Presets" }, + "example": { "message": "Example:" }, + "copyExample": { "message": "Copy example" }, + "recentAliases": { "message": "Recent Aliases" }, + "favorites": { "message": "Favorites" }, + "exportAsCsv": { "message": "Export as CSV" }, + "exportAsJson": { "message": "Export as JSON" }, + "selectAliases": { "message": "Select aliases" }, + "select": { "message": "Select" }, + "starredCount": { + "message": "$COUNT$ starred", + "placeholders": { "count": { "content": "$1" } } + }, + "deselectAll": { "message": "Deselect All" }, + "selectAll": { "message": "Select All" }, + "selectedCount": { + "message": "$COUNT$ selected", + "placeholders": { "count": { "content": "$1" } } + }, + "deleteCount": { + "message": "Delete $COUNT$", + "placeholders": { "count": { "content": "$1" } } + }, + "allCount": { + "message": "All ($COUNT$)", + "placeholders": { "count": { "content": "$1" } } + }, + "favoritesCount": { + "message": "Favorites ($COUNT$)", + "placeholders": { "count": { "content": "$1" } } + }, + "searchAliases": { "message": "Search aliases..." }, + "allTags": { "message": "All Tags" }, + "mostRecent": { "message": "Most Recent" }, + "az": { "message": "A-Z" }, + "noFavoritesYet": { "message": "No favorites yet" }, + "starEmailsHint": { + "message": "Star emails from your history to quick access them here" + }, + "noResultsFound": { "message": "No results found" }, + "differentSearchHint": { "message": "Try a different search or filter" }, + "showQrCode": { "message": "Show QR code" }, + "removeFromFavorites": { "message": "Remove from favorites" }, + "addToFavorites": { "message": "Add to favorites" }, + "showingRange": { + "message": "Showing $START$-$END$ of $TOTAL$", + "placeholders": { + "start": { "content": "$1" }, + "end": { "content": "$2" }, + "total": { "content": "$3" } + } + }, + "perPage": { + "message": "$COUNT$ / page", + "placeholders": { "count": { "content": "$1" } } + }, + "firstPage": { "message": "First page" }, + "previousPage": { "message": "Previous page" }, + "nextPage": { "message": "Next page" }, + "lastPage": { "message": "Last page" }, + "scanToCopyAlias": { "message": "Scan to copy alias" }, + "general": { "message": "General" }, + "accounts": { "message": "Accounts" }, + "changelog": { "message": "Changelog" }, + "appearanceDisplay": { "message": "Appearance & Display" }, + "theme": { "message": "Theme" }, + "themeLight": { "message": "Light" }, + "themeDark": { "message": "Dark" }, + "themeAuto": { "message": "System (Auto)" }, + "badgeCounter": { "message": "Badge Counter" }, + "badgeNone": { "message": "None (Hidden)" }, + "badgeTotal": { "message": "Total in History" }, + "badgeAllTime": { "message": "Total Generated (All Time)" }, + "badgeToday": { "message": "Created Today" }, + "badgeWeek": { "message": "This Week" }, + "copyNotifications": { "message": "Copy notifications" }, + "aliasGeneration": { "message": "Alias Generation" }, + "randomAliasFormat": { "message": "Random Alias Format" }, + "autoSaveLimit": { "message": "Auto-save Limit" }, + "aliasesLimit": { + "message": "$COUNT$ aliases", + "placeholders": { "count": { "content": "$1" } } + }, + "customPresets": { "message": "Custom Presets" }, + "label": { "message": "Label" }, + "tag": { "message": "Tag" }, + "addPreset": { "message": "+ Add Preset" }, + "dataManagement": { "message": "Data Management" }, + "export": { "message": "Export" }, + "import": { "message": "Import" }, + "clear": { "message": "Clear" }, + "resetSettings": { "message": "Reset all settings to default" }, + "resetSettingsTitle": { "message": "Reset settings?" }, + "resetSettingsMessage": { + "message": "This will restore every setting to its default value." + }, + "reset": { "message": "Reset" }, + "clearHistoryTitle": { "message": "Clear recent aliases?" }, + "clearHistoryMessage": { + "message": "This removes all aliases from the recent history list." + }, + "emailAccounts": { "message": "Email Accounts" }, + "manageAccountsDescription": { + "message": "Manage your Gmail accounts. Each account has its own history, statistics, and favorites." + }, + "noAccountsFound": { + "message": "No accounts found. Please add an account from the main screen." + }, + "emailAddress": { "message": "Email Address" }, + "accountLabel": { "message": "Account label" }, + "emailAddressPlaceholder": { "message": "your.email@gmail.com" }, + "emailChangeWarning": { + "message": "Changing email will migrate all data to the new email address" + }, + "saveChanges": { "message": "Save Changes" }, + "active": { "message": "Active" }, + "editAccount": { "message": "Edit account" }, + "cannotDeleteLastAccountTitle": { + "message": "Cannot delete the last account" + }, + "deleteThisAccount": { "message": "Delete this account" }, + "deleteAccountTitle": { "message": "Delete account?" }, + "deleteAccountMessage": { + "message": "Delete \"$LABEL$\" ($EMAIL$)?\n\nThis will permanently delete:\n- All history for this account\n- All statistics\n- All favorites\n\nThis action cannot be undone.", + "placeholders": { + "label": { "content": "$1" }, + "email": { "content": "$2" } + } + }, + "delete": { "message": "Delete" }, + "changeAccountEmailTitle": { "message": "Change account email?" }, + "changeAccountEmailMessage": { + "message": "Change email from\n$OLD_EMAIL$\nto\n$NEW_EMAIL$?\n\nThis will:\n- Migrate all history, statistics, and favorites to the new email\n- Update the account email\n- Delete data associated with the old email\n\nContinue?", + "placeholders": { + "old_email": { "content": "$1" }, + "new_email": { "content": "$2" } + } + }, + "changeEmail": { "message": "Change email" }, + "menuRandomEmailAlias": { "message": "Random Email Alias" }, + "menuCustomTags": { "message": "Custom Tags" }, + "menuNoPresets": { "message": "No presets - Add in Settings" }, + "menuGmailTricks": { "message": "Gmail Tricks" }, + "menuDotVariation": { "message": "Dot Variation" }, + "menuGooglemailDomain": { "message": "Googlemail Domain" }, + "menuRemoveAllDots": { "message": "Remove All Dots" }, + "toastPresetAdded": { "message": "Preset added" }, + "toastPresetRemoved": { "message": "Preset removed" }, + "toastSettingsExported": { "message": "Settings exported" }, + "toastSettingsImported": { "message": "Settings imported" }, + "toastImportFailed": { "message": "Import failed - invalid file" }, + "toastSettingsReset": { "message": "Settings reset to default" }, + "toastAccountSwitched": { "message": "Account switched" }, + "toastAccountDeleted": { "message": "Account deleted" }, + "toastAccountUpdated": { "message": "Account updated" }, + "toastAccountAdded": { + "message": "$LABEL$ added", + "placeholders": { "label": { "content": "$1" } } + }, + "errorLabelRequired": { "message": "Label cannot be empty" }, + "errorInvalidEmail": { "message": "Please enter a valid email address" }, + "errorDuplicateEmail": { + "message": "This email address is already used by another account" + }, + "errorEnterEmail": { "message": "Please enter an email address" }, + "errorAccountExists": { "message": "This account already exists" }, + "toastQrFailed": { "message": "Failed to generate QR code" }, + "toastExportedAliases": { + "message": "Exported $COUNT$ aliases", + "placeholders": { "count": { "content": "$1" } } + }, + "toastDeletedAliases": { + "message": "Deleted $COUNT$ aliases", + "placeholders": { "count": { "content": "$1" } } + }, + "toastHistoryCleared": { "message": "History cleared" }, + "toastFavoriteAdded": { "message": "Added to favorites" }, + "toastFavoriteRemoved": { "message": "Removed from favorites" }, + "toastCopiedEmail": { + "message": "Copied $EMAIL$", + "placeholders": { "email": { "content": "$1" } } + }, + "toastCopyFailed": { "message": "Failed to copy" }, + "welcomeTitle": { "message": "Welcome to Gmail Alias Toolkit" }, + "welcomeSubtitle": { + "message": "Generate unlimited email aliases for privacy and organization" + }, + "letsGetStarted": { "message": "Let's get started" }, + "enterGmailAddress": { "message": "Enter your Gmail address" }, + "pressTabForGmail": { + "message": "Press $KEY$ for @gmail.com", + "placeholders": { "key": { "content": "$1" } } + }, + "settingUp": { "message": "Setting up..." }, + "getStarted": { "message": "Get Started" }, + "advancedSetup": { "message": "Advanced Setup in Settings" }, + "whatYouCanDo": { "message": "What you can do:" }, + "featurePrivateEmail": { "message": "Private Email Generator" }, + "featureCustomTags": { "message": "Custom Tags & Presets" }, + "featureGmailTricks": { "message": "Gmail Advanced Tricks" }, + "welcomeFooter": { + "message": "All data is stored locally. No tracking, no server." + }, + "favoritesSaved": { + "message": "$COUNT$ saved", + "placeholders": { "count": { "content": "$1" } } + }, + "clickStarHint": { + "message": "Click star on any alias in history to add it here" + } +} diff --git a/public/_locales/fr/messages.json b/public/_locales/fr/messages.json new file mode 100644 index 0000000..0e6523a --- /dev/null +++ b/public/_locales/fr/messages.json @@ -0,0 +1,697 @@ +{ + "extensionName": { + "message": "Gmail Alias Toolkit" + }, + "extensionDescription": { + "message": "Générez et gérez des alias Gmail avec l’adressage plus et des préréglages" + }, + "headerSubtitle": { + "message": "Générez des alias avec l’adressage plus" + }, + "settings": { + "message": "Paramètres" + }, + "back": { + "message": "Retour" + }, + "close": { + "message": "Fermer" + }, + "cancel": { + "message": "Annuler" + }, + "copy": { + "message": "Copier" + }, + "copyAll": { + "message": "Tout copier" + }, + "copyToClipboard": { + "message": "Copier dans le presse-papiers" + }, + "activeGmailAddress": { + "message": "Adresse Gmail active" + }, + "addNewAccount": { + "message": "Ajouter un nouveau compte" + }, + "addAccount": { + "message": "Ajouter un compte" + }, + "addNewAccountTitle": { + "message": "Ajouter un nouveau compte" + }, + "emailPlaceholder": { + "message": "votre.email" + }, + "accountLabelPlaceholder": { + "message": "Libellé (facultatif, ex. Travail, Personnel)" + }, + "pressTabToAddGmail": { + "message": "Appuyez sur $KEY$ pour ajouter @gmail.com", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "gmailWarning": { + "message": "Cela ne ressemble pas à une adresse Gmail. L’adressage plus fonctionne mieux avec Gmail." + }, + "random": { + "message": "Aléatoire" + }, + "customTags": { + "message": "Tags personnalisés" + }, + "gmailTricks": { + "message": "Astuces Gmail" + }, + "tabTagsShort": { + "message": "Tags" + }, + "tabTricksShort": { + "message": "Astuces" + }, + "generating": { + "message": "Génération" + }, + "copied": { + "message": "Copié" + }, + "switchToLightMode": { + "message": "Passer au mode clair" + }, + "switchToDarkMode": { + "message": "Passer au mode sombre" + }, + "aliasColumn": { + "message": "Alias" + }, + "copyEmailTooltip": { + "message": "$EMAIL$ - cliquer pour copier", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "pageLabel": { + "message": "Page $PAGE$", + "placeholders": { + "page": { + "content": "$1" + } + } + }, + "dotTrick": { + "message": "Astuce points" + }, + "plusTags": { + "message": "Tags +" + }, + "googlemail": { + "message": "Googlemail" + }, + "removeDots": { + "message": "Supprimer les points" + }, + "dotPlus": { + "message": "Points + tags" + }, + "allCombos": { + "message": "Toutes les combinaisons" + }, + "numberOfVariations": { + "message": "Nombre de variantes" + }, + "randomizeDotPositions": { + "message": "Positions des points aléatoires" + }, + "sequential": { + "message": "Séquentiel" + }, + "generateTricks": { + "message": "Générer les astuces" + }, + "generatedVariations": { + "message": "Variantes générées" + }, + "gmailTrickInfoLabel": { + "message": "Astuce Gmail :" + }, + "gmailTrickInfo": { + "message": "Les points sont ignorés et tout ce qui suit + arrive dans la même boîte" + }, + "format": { + "message": "Format" + }, + "privateMailFormat": { + "message": "Private Mail (private-mail-xxxx)" + }, + "randomCharactersFormat": { + "message": "Caractères aléatoires (abc123xy)" + }, + "randomWordsFormat": { + "message": "Mots aléatoires (happy-fox-42)" + }, + "timestampFormat": { + "message": "Horodatage (1234567890)" + }, + "numberOfAliases": { + "message": "Nombre d’alias" + }, + "generateRandomAliases": { + "message": "Générer $COUNT$ alias aléatoire(s)", + "placeholders": { + "count": { + "content": "$1" + }, + "plural": { + "content": "$2" + } + } + }, + "generatedAliases": { + "message": "Alias générés" + }, + "totalCount": { + "message": "$COUNT$ au total", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "copiedAliases": { + "message": "$COUNT$ alias copiés !", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "failedToCopy": { + "message": "Échec de la copie" + }, + "formatPrivateMail": { + "message": "Format : private-mail-xxxx" + }, + "formatAlphanumeric": { + "message": "8 caractères aléatoires" + }, + "formatWords": { + "message": "2 mots aléatoires" + }, + "formatTimestamp": { + "message": "Horodatage Unix" + }, + "tagPlaceholder": { + "message": "Saisir un tag (ex. shopping, travail)" + }, + "generate": { + "message": "Générer" + }, + "yourPresets": { + "message": "Vos préréglages" + }, + "example": { + "message": "Exemple :" + }, + "copyExample": { + "message": "Copier l’exemple" + }, + "recentAliases": { + "message": "Alias récents" + }, + "favorites": { + "message": "Favoris" + }, + "exportAsCsv": { + "message": "Exporter en CSV" + }, + "exportAsJson": { + "message": "Exporter en JSON" + }, + "selectAliases": { + "message": "Sélectionner des alias" + }, + "select": { + "message": "Sélectionner" + }, + "starredCount": { + "message": "$COUNT$ favoris", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deselectAll": { + "message": "Tout désélectionner" + }, + "selectAll": { + "message": "Tout sélectionner" + }, + "selectedCount": { + "message": "$COUNT$ sélectionné(s)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deleteCount": { + "message": "Supprimer $COUNT$", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "allCount": { + "message": "Tous ($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "favoritesCount": { + "message": "Favoris ($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "searchAliases": { + "message": "Rechercher des alias..." + }, + "allTags": { + "message": "Tous les tags" + }, + "mostRecent": { + "message": "Les plus récents" + }, + "az": { + "message": "A-Z" + }, + "noFavoritesYet": { + "message": "Aucun favori pour le moment" + }, + "starEmailsHint": { + "message": "Ajoutez des e-mails de votre historique aux favoris pour y accéder rapidement ici" + }, + "noResultsFound": { + "message": "Aucun résultat trouvé" + }, + "differentSearchHint": { + "message": "Essayez une autre recherche ou un autre filtre" + }, + "showQrCode": { + "message": "Afficher le code QR" + }, + "removeFromFavorites": { + "message": "Retirer des favoris" + }, + "addToFavorites": { + "message": "Ajouter aux favoris" + }, + "showingRange": { + "message": "Affichage de $START$ à $END$ sur $TOTAL$", + "placeholders": { + "start": { + "content": "$1" + }, + "end": { + "content": "$2" + }, + "total": { + "content": "$3" + } + } + }, + "perPage": { + "message": "$COUNT$ / page", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "firstPage": { + "message": "Première page" + }, + "previousPage": { + "message": "Page précédente" + }, + "nextPage": { + "message": "Page suivante" + }, + "lastPage": { + "message": "Dernière page" + }, + "scanToCopyAlias": { + "message": "Scanner pour copier l’alias" + }, + "general": { + "message": "Général" + }, + "accounts": { + "message": "Comptes" + }, + "changelog": { + "message": "Journal des modifications" + }, + "appearanceDisplay": { + "message": "Apparence et affichage" + }, + "theme": { + "message": "Thème" + }, + "themeLight": { + "message": "Clair" + }, + "themeDark": { + "message": "Sombre" + }, + "themeAuto": { + "message": "Système (Auto)" + }, + "badgeCounter": { + "message": "Compteur de badge" + }, + "badgeNone": { + "message": "Aucun (masqué)" + }, + "badgeTotal": { + "message": "Total dans l’historique" + }, + "badgeAllTime": { + "message": "Total généré (tous temps)" + }, + "badgeToday": { + "message": "Créés aujourd’hui" + }, + "badgeWeek": { + "message": "Cette semaine" + }, + "copyNotifications": { + "message": "Notifications de copie" + }, + "aliasGeneration": { + "message": "Génération d’alias" + }, + "randomAliasFormat": { + "message": "Format d’alias aléatoire" + }, + "autoSaveLimit": { + "message": "Limite d’enregistrement automatique" + }, + "aliasesLimit": { + "message": "$COUNT$ alias", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "customPresets": { + "message": "Préréglages personnalisés" + }, + "label": { + "message": "Libellé" + }, + "tag": { + "message": "Tag" + }, + "addPreset": { + "message": "+ Ajouter un préréglage" + }, + "dataManagement": { + "message": "Gestion des données" + }, + "export": { + "message": "Exporter" + }, + "import": { + "message": "Importer" + }, + "clear": { + "message": "Effacer" + }, + "resetSettings": { + "message": "Réinitialiser tous les paramètres par défaut" + }, + "resetSettingsTitle": { + "message": "Réinitialiser les paramètres ?" + }, + "resetSettingsMessage": { + "message": "Tous les paramètres seront restaurés à leur valeur par défaut." + }, + "reset": { + "message": "Réinitialiser" + }, + "clearHistoryTitle": { + "message": "Effacer les alias récents ?" + }, + "clearHistoryMessage": { + "message": "Cela supprime tous les alias de la liste d’historique récent." + }, + "emailAccounts": { + "message": "Comptes e-mail" + }, + "manageAccountsDescription": { + "message": "Gérez vos comptes Gmail. Chaque compte possède son propre historique, ses statistiques et ses favoris." + }, + "noAccountsFound": { + "message": "Aucun compte trouvé. Veuillez ajouter un compte depuis l’écran principal." + }, + "emailAddress": { + "message": "Adresse e-mail" + }, + "accountLabel": { + "message": "Libellé du compte" + }, + "emailAddressPlaceholder": { + "message": "votre.email@gmail.com" + }, + "emailChangeWarning": { + "message": "La modification de l’e-mail migrera toutes les données vers la nouvelle adresse e-mail" + }, + "saveChanges": { + "message": "Enregistrer les modifications" + }, + "active": { + "message": "Actif" + }, + "editAccount": { + "message": "Modifier le compte" + }, + "cannotDeleteLastAccountTitle": { + "message": "Impossible de supprimer le dernier compte" + }, + "deleteThisAccount": { + "message": "Supprimer ce compte" + }, + "deleteAccountTitle": { + "message": "Supprimer le compte ?" + }, + "deleteAccountMessage": { + "message": "Supprimer « $LABEL$ » ($EMAIL$) ?\n\nCela supprimera définitivement :\n- Tout l’historique de ce compte\n- Toutes les statistiques\n- Tous les favoris\n\nCette action est irréversible.", + "placeholders": { + "label": { + "content": "$1" + }, + "email": { + "content": "$2" + } + } + }, + "delete": { + "message": "Supprimer" + }, + "changeAccountEmailTitle": { + "message": "Changer l’e-mail du compte ?" + }, + "changeAccountEmailMessage": { + "message": "Changer l’e-mail de\n$OLD_EMAIL$\nvers\n$NEW_EMAIL$ ?\n\nCela va :\n- Migrer tout l’historique, les statistiques et les favoris vers le nouvel e-mail\n- Mettre à jour l’e-mail du compte\n- Supprimer les données associées à l’ancien e-mail\n\nContinuer ?", + "placeholders": { + "old_email": { + "content": "$1" + }, + "new_email": { + "content": "$2" + } + } + }, + "changeEmail": { + "message": "Changer l’e-mail" + }, + "menuRandomEmailAlias": { + "message": "Alias e-mail aléatoire" + }, + "menuCustomTags": { + "message": "Tags personnalisés" + }, + "menuNoPresets": { + "message": "Aucun préréglage - Ajoutez-en dans les paramètres" + }, + "menuGmailTricks": { + "message": "Astuces Gmail" + }, + "menuDotVariation": { + "message": "Variation avec points" + }, + "menuGooglemailDomain": { + "message": "Domaine Googlemail" + }, + "menuRemoveAllDots": { + "message": "Supprimer tous les points" + }, + "toastPresetAdded": { + "message": "Préréglage ajouté" + }, + "toastPresetRemoved": { + "message": "Préréglage supprimé" + }, + "toastSettingsExported": { + "message": "Paramètres exportés" + }, + "toastSettingsImported": { + "message": "Paramètres importés" + }, + "toastImportFailed": { + "message": "Échec de l’importation - fichier invalide" + }, + "toastSettingsReset": { + "message": "Paramètres réinitialisés par défaut" + }, + "toastAccountSwitched": { + "message": "Compte changé" + }, + "toastAccountDeleted": { + "message": "Compte supprimé" + }, + "toastAccountUpdated": { + "message": "Compte mis à jour" + }, + "toastAccountAdded": { + "message": "$LABEL$ ajouté", + "placeholders": { + "label": { + "content": "$1" + } + } + }, + "errorLabelRequired": { + "message": "Le libellé ne peut pas être vide" + }, + "errorInvalidEmail": { + "message": "Veuillez saisir une adresse e-mail valide" + }, + "errorDuplicateEmail": { + "message": "Cette adresse e-mail est déjà utilisée par un autre compte" + }, + "errorEnterEmail": { + "message": "Veuillez saisir une adresse e-mail" + }, + "errorAccountExists": { + "message": "Ce compte existe déjà" + }, + "toastQrFailed": { + "message": "Échec de la génération du code QR" + }, + "toastExportedAliases": { + "message": "$COUNT$ alias exportés", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastDeletedAliases": { + "message": "$COUNT$ alias supprimés", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastHistoryCleared": { + "message": "Historique effacé" + }, + "toastFavoriteAdded": { + "message": "Ajouté aux favoris" + }, + "toastFavoriteRemoved": { + "message": "Retiré des favoris" + }, + "toastCopiedEmail": { + "message": "$EMAIL$ copié", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "toastCopyFailed": { + "message": "Échec de la copie" + }, + "welcomeTitle": { + "message": "Bienvenue dans Gmail Alias Toolkit" + }, + "welcomeSubtitle": { + "message": "Générez des alias e-mail illimités pour la confidentialité et l’organisation" + }, + "letsGetStarted": { + "message": "Commençons" + }, + "enterGmailAddress": { + "message": "Saisissez votre adresse Gmail" + }, + "pressTabForGmail": { + "message": "Appuyez sur $KEY$ pour @gmail.com", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "settingUp": { + "message": "Configuration en cours..." + }, + "getStarted": { + "message": "Commencer" + }, + "advancedSetup": { + "message": "Configuration avancée dans les paramètres" + }, + "whatYouCanDo": { + "message": "Ce que vous pouvez faire :" + }, + "featurePrivateEmail": { + "message": "Générateur d’e-mail privé" + }, + "featureCustomTags": { + "message": "Tags et préréglages personnalisés" + }, + "featureGmailTricks": { + "message": "Astuces Gmail avancées" + }, + "welcomeFooter": { + "message": "Toutes les données sont stockées localement. Aucun suivi, aucun serveur." + }, + "favoritesSaved": { + "message": "$COUNT$ enregistré(s)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "clickStarHint": { + "message": "Cliquez sur l’étoile d’un alias dans l’historique pour l’ajouter ici" + } +} diff --git a/public/_locales/hi/messages.json b/public/_locales/hi/messages.json new file mode 100644 index 0000000..780a725 --- /dev/null +++ b/public/_locales/hi/messages.json @@ -0,0 +1,697 @@ +{ + "extensionName": { + "message": "Gmail Alias Toolkit" + }, + "extensionDescription": { + "message": "प्लस एड्रेसिंग और प्रीसेट के साथ Gmail उपनाम बनाएं और प्रबंधित करें" + }, + "headerSubtitle": { + "message": "प्लस एड्रेसिंग के साथ उपनाम बनाएं" + }, + "settings": { + "message": "सेटिंग्स" + }, + "back": { + "message": "वापस" + }, + "close": { + "message": "बंद करें" + }, + "cancel": { + "message": "रद्द करें" + }, + "copy": { + "message": "कॉपी करें" + }, + "copyAll": { + "message": "सभी कॉपी करें" + }, + "copyToClipboard": { + "message": "क्लिपबोर्ड पर कॉपी करें" + }, + "activeGmailAddress": { + "message": "सक्रिय Gmail पता" + }, + "addNewAccount": { + "message": "नया खाता जोड़ें" + }, + "addAccount": { + "message": "खाता जोड़ें" + }, + "addNewAccountTitle": { + "message": "नया खाता जोड़ें" + }, + "emailPlaceholder": { + "message": "your.email" + }, + "accountLabelPlaceholder": { + "message": "लेबल (वैकल्पिक, जैसे Work, Personal)" + }, + "pressTabToAddGmail": { + "message": "@gmail.com जोड़ने के लिए $KEY$ दबाएं", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "gmailWarning": { + "message": "यह Gmail पता नहीं लग रहा है। प्लस एड्रेसिंग Gmail के साथ सबसे अच्छी तरह काम करती है।" + }, + "random": { + "message": "यादृच्छिक" + }, + "customTags": { + "message": "कस्टम टैग" + }, + "gmailTricks": { + "message": "Gmail ट्रिक्स" + }, + "tabTagsShort": { + "message": "टैग" + }, + "tabTricksShort": { + "message": "ट्रिक्स" + }, + "generating": { + "message": "बनाया जा रहा है" + }, + "copied": { + "message": "कॉपी हो गया" + }, + "switchToLightMode": { + "message": "लाइट मोड पर जाएं" + }, + "switchToDarkMode": { + "message": "डार्क मोड पर जाएं" + }, + "aliasColumn": { + "message": "उपनाम" + }, + "copyEmailTooltip": { + "message": "$EMAIL$ - कॉपी करने के लिए क्लिक करें", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "pageLabel": { + "message": "पृष्ठ $PAGE$", + "placeholders": { + "page": { + "content": "$1" + } + } + }, + "dotTrick": { + "message": "डॉट ट्रिक" + }, + "plusTags": { + "message": "प्लस (+) टैग" + }, + "googlemail": { + "message": "Googlemail" + }, + "removeDots": { + "message": "डॉट हटाएं" + }, + "dotPlus": { + "message": "डॉट + प्लस" + }, + "allCombos": { + "message": "सभी कॉम्बो" + }, + "numberOfVariations": { + "message": "वेरिएशन की संख्या" + }, + "randomizeDotPositions": { + "message": "डॉट की स्थिति रैंडम करें" + }, + "sequential": { + "message": "क्रमवार" + }, + "generateTricks": { + "message": "ट्रिक्स बनाएं" + }, + "generatedVariations": { + "message": "बने हुए वेरिएशन" + }, + "gmailTrickInfoLabel": { + "message": "Gmail ट्रिक:" + }, + "gmailTrickInfo": { + "message": "डॉट्स को अनदेखा किया जाता है और + के बाद की हर चीज उसी इनबॉक्स में जाती है" + }, + "format": { + "message": "फ़ॉर्मैट" + }, + "privateMailFormat": { + "message": "Private Mail (private-mail-xxxx)" + }, + "randomCharactersFormat": { + "message": "यादृच्छिक वर्ण (abc123xy)" + }, + "randomWordsFormat": { + "message": "यादृच्छिक शब्द (happy-fox-42)" + }, + "timestampFormat": { + "message": "टाइमस्टैम्प (1234567890)" + }, + "numberOfAliases": { + "message": "उपनामों की संख्या" + }, + "generateRandomAliases": { + "message": "$COUNT$ यादृच्छिक उपनाम बनाएं", + "placeholders": { + "count": { + "content": "$1" + }, + "plural": { + "content": "$2" + } + } + }, + "generatedAliases": { + "message": "बनाए गए उपनाम" + }, + "totalCount": { + "message": "कुल $COUNT$", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "copiedAliases": { + "message": "$COUNT$ उपनाम कॉपी किए गए!", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "failedToCopy": { + "message": "कॉपी करने में विफल" + }, + "formatPrivateMail": { + "message": "फ़ॉर्मैट: private-mail-xxxx" + }, + "formatAlphanumeric": { + "message": "8 यादृच्छिक वर्ण" + }, + "formatWords": { + "message": "2 यादृच्छिक शब्द" + }, + "formatTimestamp": { + "message": "Unix टाइमस्टैम्प" + }, + "tagPlaceholder": { + "message": "टैग दर्ज करें (जैसे shopping, work)" + }, + "generate": { + "message": "बनाएं" + }, + "yourPresets": { + "message": "आपके प्रीसेट" + }, + "example": { + "message": "उदाहरण:" + }, + "copyExample": { + "message": "उदाहरण कॉपी करें" + }, + "recentAliases": { + "message": "हाल के उपनाम" + }, + "favorites": { + "message": "पसंदीदा" + }, + "exportAsCsv": { + "message": "CSV के रूप में निर्यात करें" + }, + "exportAsJson": { + "message": "JSON के रूप में निर्यात करें" + }, + "selectAliases": { + "message": "उपनाम चुनें" + }, + "select": { + "message": "चुनें" + }, + "starredCount": { + "message": "$COUNT$ स्टार किए गए", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deselectAll": { + "message": "सभी चयन हटाएं" + }, + "selectAll": { + "message": "सभी चुनें" + }, + "selectedCount": { + "message": "$COUNT$ चुने गए", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deleteCount": { + "message": "$COUNT$ हटाएं", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "allCount": { + "message": "सभी ($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "favoritesCount": { + "message": "पसंदीदा ($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "searchAliases": { + "message": "उपनाम खोजें..." + }, + "allTags": { + "message": "सभी टैग" + }, + "mostRecent": { + "message": "सबसे हाल के" + }, + "az": { + "message": "A-Z" + }, + "noFavoritesYet": { + "message": "अभी कोई पसंदीदा नहीं" + }, + "starEmailsHint": { + "message": "यहां तुरंत पहुंचने के लिए अपने इतिहास से ईमेल को स्टार करें" + }, + "noResultsFound": { + "message": "कोई परिणाम नहीं मिला" + }, + "differentSearchHint": { + "message": "कोई दूसरी खोज या फ़िल्टर आज़माएं" + }, + "showQrCode": { + "message": "QR कोड दिखाएं" + }, + "removeFromFavorites": { + "message": "पसंदीदा से हटाएं" + }, + "addToFavorites": { + "message": "पसंदीदा में जोड़ें" + }, + "showingRange": { + "message": "$TOTAL$ में से $START$-$END$ दिखाए जा रहे हैं", + "placeholders": { + "start": { + "content": "$1" + }, + "end": { + "content": "$2" + }, + "total": { + "content": "$3" + } + } + }, + "perPage": { + "message": "$COUNT$ / पेज", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "firstPage": { + "message": "पहला पेज" + }, + "previousPage": { + "message": "पिछला पेज" + }, + "nextPage": { + "message": "अगला पेज" + }, + "lastPage": { + "message": "अंतिम पेज" + }, + "scanToCopyAlias": { + "message": "उपनाम कॉपी करने के लिए स्कैन करें" + }, + "general": { + "message": "सामान्य" + }, + "accounts": { + "message": "खाते" + }, + "changelog": { + "message": "परिवर्तन लॉग" + }, + "appearanceDisplay": { + "message": "दिखावट और प्रदर्शन" + }, + "theme": { + "message": "थीम" + }, + "themeLight": { + "message": "लाइट" + }, + "themeDark": { + "message": "डार्क" + }, + "themeAuto": { + "message": "सिस्टम (ऑटो)" + }, + "badgeCounter": { + "message": "बैज काउंटर" + }, + "badgeNone": { + "message": "कोई नहीं (छिपा हुआ)" + }, + "badgeTotal": { + "message": "इतिहास में कुल" + }, + "badgeAllTime": { + "message": "कुल बनाए गए (सभी समय)" + }, + "badgeToday": { + "message": "आज बनाए गए" + }, + "badgeWeek": { + "message": "इस सप्ताह" + }, + "copyNotifications": { + "message": "कॉपी सूचनाएं" + }, + "aliasGeneration": { + "message": "उपनाम बनाना" + }, + "randomAliasFormat": { + "message": "यादृच्छिक उपनाम फ़ॉर्मैट" + }, + "autoSaveLimit": { + "message": "ऑटो-सेव सीमा" + }, + "aliasesLimit": { + "message": "$COUNT$ उपनाम", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "customPresets": { + "message": "कस्टम प्रीसेट" + }, + "label": { + "message": "लेबल" + }, + "tag": { + "message": "टैग" + }, + "addPreset": { + "message": "+ प्रीसेट जोड़ें" + }, + "dataManagement": { + "message": "डेटा प्रबंधन" + }, + "export": { + "message": "निर्यात" + }, + "import": { + "message": "आयात" + }, + "clear": { + "message": "साफ़ करें" + }, + "resetSettings": { + "message": "सभी सेटिंग्स को डिफ़ॉल्ट पर रीसेट करें" + }, + "resetSettingsTitle": { + "message": "सेटिंग्स रीसेट करें?" + }, + "resetSettingsMessage": { + "message": "यह हर सेटिंग को उसके डिफ़ॉल्ट मान पर वापस कर देगा।" + }, + "reset": { + "message": "रीसेट" + }, + "clearHistoryTitle": { + "message": "हाल के उपनाम साफ़ करें?" + }, + "clearHistoryMessage": { + "message": "यह हाल की इतिहास सूची से सभी उपनाम हटा देता है।" + }, + "emailAccounts": { + "message": "ईमेल खाते" + }, + "manageAccountsDescription": { + "message": "अपने Gmail खाते प्रबंधित करें। हर खाते का अपना इतिहास, आंकड़े और पसंदीदा होते हैं।" + }, + "noAccountsFound": { + "message": "कोई खाता नहीं मिला। कृपया मुख्य स्क्रीन से खाता जोड़ें।" + }, + "emailAddress": { + "message": "ईमेल पता" + }, + "accountLabel": { + "message": "खाता लेबल" + }, + "emailAddressPlaceholder": { + "message": "your.email@gmail.com" + }, + "emailChangeWarning": { + "message": "ईमेल बदलने पर सभी डेटा नए ईमेल पते पर माइग्रेट हो जाएगा" + }, + "saveChanges": { + "message": "बदलाव सहेजें" + }, + "active": { + "message": "सक्रिय" + }, + "editAccount": { + "message": "खाता संपादित करें" + }, + "cannotDeleteLastAccountTitle": { + "message": "अंतिम खाता हटाया नहीं जा सकता" + }, + "deleteThisAccount": { + "message": "यह खाता हटाएं" + }, + "deleteAccountTitle": { + "message": "खाता हटाएं?" + }, + "deleteAccountMessage": { + "message": "“$LABEL$” ($EMAIL$) हटाएं?\n\nयह स्थायी रूप से हटाएगा:\n- इस खाते का पूरा इतिहास\n- सभी आंकड़े\n- सभी पसंदीदा\n\nयह कार्रवाई पूर्ववत नहीं की जा सकती।", + "placeholders": { + "label": { + "content": "$1" + }, + "email": { + "content": "$2" + } + } + }, + "delete": { + "message": "हटाएं" + }, + "changeAccountEmailTitle": { + "message": "खाते का ईमेल बदलें?" + }, + "changeAccountEmailMessage": { + "message": "ईमेल बदलें\n$OLD_EMAIL$\nसे\n$NEW_EMAIL$\nमें?\n\nयह करेगा:\n- पूरा इतिहास, आंकड़े और पसंदीदा नए ईमेल में माइग्रेट करेगा\n- खाते का ईमेल अपडेट करेगा\n- पुराने ईमेल से जुड़े डेटा को हटा देगा\n\nजारी रखें?", + "placeholders": { + "old_email": { + "content": "$1" + }, + "new_email": { + "content": "$2" + } + } + }, + "changeEmail": { + "message": "ईमेल बदलें" + }, + "menuRandomEmailAlias": { + "message": "यादृच्छिक ईमेल उपनाम" + }, + "menuCustomTags": { + "message": "कस्टम टैग" + }, + "menuNoPresets": { + "message": "कोई प्रीसेट नहीं - सेटिंग्स में जोड़ें" + }, + "menuGmailTricks": { + "message": "Gmail ट्रिक्स" + }, + "menuDotVariation": { + "message": "डॉट वैरिएशन" + }, + "menuGooglemailDomain": { + "message": "Googlemail डोमेन" + }, + "menuRemoveAllDots": { + "message": "सभी डॉट हटाएं" + }, + "toastPresetAdded": { + "message": "प्रीसेट जोड़ा गया" + }, + "toastPresetRemoved": { + "message": "प्रीसेट हटाया गया" + }, + "toastSettingsExported": { + "message": "सेटिंग्स निर्यात की गईं" + }, + "toastSettingsImported": { + "message": "सेटिंग्स आयात की गईं" + }, + "toastImportFailed": { + "message": "आयात विफल - अमान्य फ़ाइल" + }, + "toastSettingsReset": { + "message": "सेटिंग्स डिफ़ॉल्ट पर रीसेट की गईं" + }, + "toastAccountSwitched": { + "message": "खाता बदला गया" + }, + "toastAccountDeleted": { + "message": "खाता हटाया गया" + }, + "toastAccountUpdated": { + "message": "खाता अपडेट किया गया" + }, + "toastAccountAdded": { + "message": "$LABEL$ जोड़ा गया", + "placeholders": { + "label": { + "content": "$1" + } + } + }, + "errorLabelRequired": { + "message": "लेबल खाली नहीं हो सकता" + }, + "errorInvalidEmail": { + "message": "कृपया एक मान्य ईमेल पता दर्ज करें" + }, + "errorDuplicateEmail": { + "message": "यह ईमेल पता पहले से किसी दूसरे खाते में उपयोग हो रहा है" + }, + "errorEnterEmail": { + "message": "कृपया एक ईमेल पता दर्ज करें" + }, + "errorAccountExists": { + "message": "यह खाता पहले से मौजूद है" + }, + "toastQrFailed": { + "message": "QR कोड बनाने में विफल" + }, + "toastExportedAliases": { + "message": "$COUNT$ उपनाम निर्यात किए गए", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastDeletedAliases": { + "message": "$COUNT$ उपनाम हटाए गए", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastHistoryCleared": { + "message": "इतिहास साफ़ किया गया" + }, + "toastFavoriteAdded": { + "message": "पसंदीदा में जोड़ा गया" + }, + "toastFavoriteRemoved": { + "message": "पसंदीदा से हटाया गया" + }, + "toastCopiedEmail": { + "message": "$EMAIL$ कॉपी किया गया", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "toastCopyFailed": { + "message": "कॉपी करने में विफल" + }, + "welcomeTitle": { + "message": "Gmail Alias Toolkit में आपका स्वागत है" + }, + "welcomeSubtitle": { + "message": "गोपनीयता और संगठन के लिए असीमित ईमेल उपनाम बनाएं" + }, + "letsGetStarted": { + "message": "चलिए शुरू करें" + }, + "enterGmailAddress": { + "message": "अपना Gmail पता दर्ज करें" + }, + "pressTabForGmail": { + "message": "@gmail.com के लिए $KEY$ दबाएं", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "settingUp": { + "message": "सेटअप हो रहा है..." + }, + "getStarted": { + "message": "शुरू करें" + }, + "advancedSetup": { + "message": "सेटिंग्स में उन्नत सेटअप" + }, + "whatYouCanDo": { + "message": "आप क्या कर सकते हैं:" + }, + "featurePrivateEmail": { + "message": "निजी ईमेल जनरेटर" + }, + "featureCustomTags": { + "message": "कस्टम टैग और प्रीसेट" + }, + "featureGmailTricks": { + "message": "Gmail एडवांस्ड ट्रिक्स" + }, + "welcomeFooter": { + "message": "सारा डेटा स्थानीय रूप से संग्रहीत होता है। कोई ट्रैकिंग नहीं, कोई सर्वर नहीं।" + }, + "favoritesSaved": { + "message": "$COUNT$ सहेजे गए", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "clickStarHint": { + "message": "इसे यहां जोड़ने के लिए इतिहास में किसी भी उपनाम पर स्टार क्लिक करें" + } +} diff --git a/public/_locales/ja/messages.json b/public/_locales/ja/messages.json new file mode 100644 index 0000000..9b6c0fb --- /dev/null +++ b/public/_locales/ja/messages.json @@ -0,0 +1,697 @@ +{ + "extensionName": { + "message": "Gmail Alias Toolkit" + }, + "extensionDescription": { + "message": "プラスアドレスとプリセットで Gmail エイリアスを生成・管理します" + }, + "headerSubtitle": { + "message": "プラスアドレスでエイリアスを生成" + }, + "settings": { + "message": "設定" + }, + "back": { + "message": "戻る" + }, + "close": { + "message": "閉じる" + }, + "cancel": { + "message": "キャンセル" + }, + "copy": { + "message": "コピー" + }, + "copyAll": { + "message": "すべてコピー" + }, + "copyToClipboard": { + "message": "クリップボードにコピー" + }, + "activeGmailAddress": { + "message": "有効な Gmail アドレス" + }, + "addNewAccount": { + "message": "新しいアカウントを追加" + }, + "addAccount": { + "message": "アカウントを追加" + }, + "addNewAccountTitle": { + "message": "新しいアカウントを追加" + }, + "emailPlaceholder": { + "message": "your.email" + }, + "accountLabelPlaceholder": { + "message": "ラベル(任意、例:仕事、個人)" + }, + "pressTabToAddGmail": { + "message": "$KEY$ を押して @gmail.com を追加", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "gmailWarning": { + "message": "これは Gmail アドレスではないようです。プラスアドレスは Gmail で最も効果的に機能します。" + }, + "random": { + "message": "ランダム" + }, + "customTags": { + "message": "カスタムタグ" + }, + "gmailTricks": { + "message": "Gmail の便利機能" + }, + "tabTagsShort": { + "message": "タグ" + }, + "tabTricksShort": { + "message": "小技" + }, + "generating": { + "message": "生成中" + }, + "copied": { + "message": "コピー済み" + }, + "switchToLightMode": { + "message": "ライトモードに切り替え" + }, + "switchToDarkMode": { + "message": "ダークモードに切り替え" + }, + "aliasColumn": { + "message": "エイリアス" + }, + "copyEmailTooltip": { + "message": "$EMAIL$ - クリックしてコピー", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "pageLabel": { + "message": "$PAGE$ページ", + "placeholders": { + "page": { + "content": "$1" + } + } + }, + "dotTrick": { + "message": "ドットの小技" + }, + "plusTags": { + "message": "プラス(+)タグ" + }, + "googlemail": { + "message": "Googlemail" + }, + "removeDots": { + "message": "ドットを削除" + }, + "dotPlus": { + "message": "ドット + プラス" + }, + "allCombos": { + "message": "すべての組み合わせ" + }, + "numberOfVariations": { + "message": "バリエーション数" + }, + "randomizeDotPositions": { + "message": "ドット位置をランダム化" + }, + "sequential": { + "message": "順番" + }, + "generateTricks": { + "message": "小技を生成" + }, + "generatedVariations": { + "message": "生成されたバリエーション" + }, + "gmailTrickInfoLabel": { + "message": "Gmailの小技:" + }, + "gmailTrickInfo": { + "message": "ドットは無視され、+ 以降はすべて同じ受信トレイに届きます" + }, + "format": { + "message": "形式" + }, + "privateMailFormat": { + "message": "プライベートメール(private-mail-xxxx)" + }, + "randomCharactersFormat": { + "message": "ランダム文字(abc123xy)" + }, + "randomWordsFormat": { + "message": "ランダム単語(happy-fox-42)" + }, + "timestampFormat": { + "message": "タイムスタンプ(1234567890)" + }, + "numberOfAliases": { + "message": "エイリアス数" + }, + "generateRandomAliases": { + "message": "$COUNT$ 件のランダムエイリアスを生成", + "placeholders": { + "count": { + "content": "$1" + }, + "plural": { + "content": "$2" + } + } + }, + "generatedAliases": { + "message": "生成されたエイリアス" + }, + "totalCount": { + "message": "合計 $COUNT$ 件", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "copiedAliases": { + "message": "$COUNT$ 件のエイリアスをコピーしました!", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "failedToCopy": { + "message": "コピーに失敗しました" + }, + "formatPrivateMail": { + "message": "形式:private-mail-xxxx" + }, + "formatAlphanumeric": { + "message": "8 文字のランダム文字" + }, + "formatWords": { + "message": "2 つのランダム単語" + }, + "formatTimestamp": { + "message": "Unix タイムスタンプ" + }, + "tagPlaceholder": { + "message": "タグを入力(例:shopping、work)" + }, + "generate": { + "message": "生成" + }, + "yourPresets": { + "message": "あなたのプリセット" + }, + "example": { + "message": "例:" + }, + "copyExample": { + "message": "例をコピー" + }, + "recentAliases": { + "message": "最近のエイリアス" + }, + "favorites": { + "message": "お気に入り" + }, + "exportAsCsv": { + "message": "CSV としてエクスポート" + }, + "exportAsJson": { + "message": "JSON としてエクスポート" + }, + "selectAliases": { + "message": "エイリアスを選択" + }, + "select": { + "message": "選択" + }, + "starredCount": { + "message": "$COUNT$ 件をスター付き", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deselectAll": { + "message": "選択解除" + }, + "selectAll": { + "message": "すべて選択" + }, + "selectedCount": { + "message": "$COUNT$ 件選択中", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deleteCount": { + "message": "$COUNT$ 件を削除", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "allCount": { + "message": "すべて($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "favoritesCount": { + "message": "お気に入り($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "searchAliases": { + "message": "エイリアスを検索..." + }, + "allTags": { + "message": "すべてのタグ" + }, + "mostRecent": { + "message": "最新順" + }, + "az": { + "message": "A-Z" + }, + "noFavoritesYet": { + "message": "お気に入りはまだありません" + }, + "starEmailsHint": { + "message": "履歴内のメールにスターを付けると、ここからすばやくアクセスできます" + }, + "noResultsFound": { + "message": "結果が見つかりません" + }, + "differentSearchHint": { + "message": "別の検索語またはフィルターを試してください" + }, + "showQrCode": { + "message": "QR コードを表示" + }, + "removeFromFavorites": { + "message": "お気に入りから削除" + }, + "addToFavorites": { + "message": "お気に入りに追加" + }, + "showingRange": { + "message": "$TOTAL$ 件中 $START$〜$END$ 件を表示", + "placeholders": { + "start": { + "content": "$1" + }, + "end": { + "content": "$2" + }, + "total": { + "content": "$3" + } + } + }, + "perPage": { + "message": "$COUNT$ / ページ", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "firstPage": { + "message": "最初のページ" + }, + "previousPage": { + "message": "前のページ" + }, + "nextPage": { + "message": "次のページ" + }, + "lastPage": { + "message": "最後のページ" + }, + "scanToCopyAlias": { + "message": "スキャンしてエイリアスをコピー" + }, + "general": { + "message": "一般" + }, + "accounts": { + "message": "アカウント" + }, + "changelog": { + "message": "変更履歴" + }, + "appearanceDisplay": { + "message": "外観と表示" + }, + "theme": { + "message": "テーマ" + }, + "themeLight": { + "message": "ライト" + }, + "themeDark": { + "message": "ダーク" + }, + "themeAuto": { + "message": "システム(自動)" + }, + "badgeCounter": { + "message": "バッジカウンター" + }, + "badgeNone": { + "message": "なし(非表示)" + }, + "badgeTotal": { + "message": "履歴内の合計" + }, + "badgeAllTime": { + "message": "総生成数" + }, + "badgeToday": { + "message": "今日作成" + }, + "badgeWeek": { + "message": "今週" + }, + "copyNotifications": { + "message": "コピー通知" + }, + "aliasGeneration": { + "message": "エイリアス生成" + }, + "randomAliasFormat": { + "message": "ランダムエイリアス形式" + }, + "autoSaveLimit": { + "message": "自動保存上限" + }, + "aliasesLimit": { + "message": "$COUNT$ 件のエイリアス", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "customPresets": { + "message": "カスタムプリセット" + }, + "label": { + "message": "ラベル" + }, + "tag": { + "message": "タグ" + }, + "addPreset": { + "message": "+ プリセットを追加" + }, + "dataManagement": { + "message": "データ管理" + }, + "export": { + "message": "エクスポート" + }, + "import": { + "message": "インポート" + }, + "clear": { + "message": "クリア" + }, + "resetSettings": { + "message": "すべての設定をデフォルトに戻す" + }, + "resetSettingsTitle": { + "message": "設定をリセットしますか?" + }, + "resetSettingsMessage": { + "message": "すべての設定がデフォルト値に戻ります。" + }, + "reset": { + "message": "リセット" + }, + "clearHistoryTitle": { + "message": "最近のエイリアスをクリアしますか?" + }, + "clearHistoryMessage": { + "message": "最近の履歴リストからすべてのエイリアスを削除します。" + }, + "emailAccounts": { + "message": "メールアカウント" + }, + "manageAccountsDescription": { + "message": "Gmail アカウントを管理します。各アカウントには個別の履歴、統計、お気に入りがあります。" + }, + "noAccountsFound": { + "message": "アカウントが見つかりません。メイン画面からアカウントを追加してください。" + }, + "emailAddress": { + "message": "メールアドレス" + }, + "accountLabel": { + "message": "アカウントラベル" + }, + "emailAddressPlaceholder": { + "message": "your.email@gmail.com" + }, + "emailChangeWarning": { + "message": "メールを変更すると、すべてのデータが新しいメールアドレスに移行されます" + }, + "saveChanges": { + "message": "変更を保存" + }, + "active": { + "message": "有効" + }, + "editAccount": { + "message": "アカウントを編集" + }, + "cannotDeleteLastAccountTitle": { + "message": "最後のアカウントは削除できません" + }, + "deleteThisAccount": { + "message": "このアカウントを削除" + }, + "deleteAccountTitle": { + "message": "アカウントを削除しますか?" + }, + "deleteAccountMessage": { + "message": "「$LABEL$」($EMAIL$)を削除しますか?\n\nこれにより以下が完全に削除されます:\n- このアカウントのすべての履歴\n- すべての統計\n- すべてのお気に入り\n\nこの操作は元に戻せません。", + "placeholders": { + "label": { + "content": "$1" + }, + "email": { + "content": "$2" + } + } + }, + "delete": { + "message": "削除" + }, + "changeAccountEmailTitle": { + "message": "アカウントのメールを変更しますか?" + }, + "changeAccountEmailMessage": { + "message": "メールを\n$OLD_EMAIL$\nから\n$NEW_EMAIL$\nに変更しますか?\n\nこれにより:\n- すべての履歴、統計、お気に入りが新しいメールに移行されます\n- アカウントのメールが更新されます\n- 古いメールに関連付けられたデータが削除されます\n\n続行しますか?", + "placeholders": { + "old_email": { + "content": "$1" + }, + "new_email": { + "content": "$2" + } + } + }, + "changeEmail": { + "message": "メールを変更" + }, + "menuRandomEmailAlias": { + "message": "ランダムメールエイリアス" + }, + "menuCustomTags": { + "message": "カスタムタグ" + }, + "menuNoPresets": { + "message": "プリセットなし - 設定で追加" + }, + "menuGmailTricks": { + "message": "Gmail の便利機能" + }, + "menuDotVariation": { + "message": "ドットバリエーション" + }, + "menuGooglemailDomain": { + "message": "Googlemail ドメイン" + }, + "menuRemoveAllDots": { + "message": "すべてのドットを削除" + }, + "toastPresetAdded": { + "message": "プリセットを追加しました" + }, + "toastPresetRemoved": { + "message": "プリセットを削除しました" + }, + "toastSettingsExported": { + "message": "設定をエクスポートしました" + }, + "toastSettingsImported": { + "message": "設定をインポートしました" + }, + "toastImportFailed": { + "message": "インポートに失敗しました - 無効なファイル" + }, + "toastSettingsReset": { + "message": "設定をデフォルトに戻しました" + }, + "toastAccountSwitched": { + "message": "アカウントを切り替えました" + }, + "toastAccountDeleted": { + "message": "アカウントを削除しました" + }, + "toastAccountUpdated": { + "message": "アカウントを更新しました" + }, + "toastAccountAdded": { + "message": "$LABEL$ を追加しました", + "placeholders": { + "label": { + "content": "$1" + } + } + }, + "errorLabelRequired": { + "message": "ラベルは空にできません" + }, + "errorInvalidEmail": { + "message": "有効なメールアドレスを入力してください" + }, + "errorDuplicateEmail": { + "message": "このメールアドレスは別のアカウントで既に使用されています" + }, + "errorEnterEmail": { + "message": "メールアドレスを入力してください" + }, + "errorAccountExists": { + "message": "このアカウントは既に存在します" + }, + "toastQrFailed": { + "message": "QR コードの生成に失敗しました" + }, + "toastExportedAliases": { + "message": "$COUNT$ 件のエイリアスをエクスポートしました", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastDeletedAliases": { + "message": "$COUNT$ 件のエイリアスを削除しました", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastHistoryCleared": { + "message": "履歴をクリアしました" + }, + "toastFavoriteAdded": { + "message": "お気に入りに追加しました" + }, + "toastFavoriteRemoved": { + "message": "お気に入りから削除しました" + }, + "toastCopiedEmail": { + "message": "$EMAIL$ をコピーしました", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "toastCopyFailed": { + "message": "コピーに失敗しました" + }, + "welcomeTitle": { + "message": "Gmail Alias Toolkit へようこそ" + }, + "welcomeSubtitle": { + "message": "プライバシーと整理のために無制限のメールエイリアスを生成" + }, + "letsGetStarted": { + "message": "始めましょう" + }, + "enterGmailAddress": { + "message": "Gmail アドレスを入力してください" + }, + "pressTabForGmail": { + "message": "$KEY$ を押して @gmail.com を入力", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "settingUp": { + "message": "設定中..." + }, + "getStarted": { + "message": "始める" + }, + "advancedSetup": { + "message": "設定で詳細セットアップ" + }, + "whatYouCanDo": { + "message": "できること:" + }, + "featurePrivateEmail": { + "message": "プライベートメール生成" + }, + "featureCustomTags": { + "message": "カスタムタグとプリセット" + }, + "featureGmailTricks": { + "message": "Gmail 高度な便利機能" + }, + "welcomeFooter": { + "message": "すべてのデータはローカルに保存されます。追跡なし、サーバーなし。" + }, + "favoritesSaved": { + "message": "$COUNT$ 件保存済み", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "clickStarHint": { + "message": "履歴内の任意のエイリアスのスターをクリックすると、ここに追加できます" + } +} diff --git a/public/_locales/vi/messages.json b/public/_locales/vi/messages.json new file mode 100644 index 0000000..1f71803 --- /dev/null +++ b/public/_locales/vi/messages.json @@ -0,0 +1,301 @@ +{ + "extensionName": { "message": "Bộ công cụ bí danh Gmail" }, + "extensionDescription": { + "message": "Tạo và quản lý bí danh Gmail bằng plus addressing và mẫu có sẵn" + }, + "headerSubtitle": { "message": "Tạo bí danh bằng plus addressing" }, + "settings": { "message": "Cài đặt" }, + "back": { "message": "Quay lại" }, + "close": { "message": "Đóng" }, + "cancel": { "message": "Hủy" }, + "copy": { "message": "Sao chép" }, + "copyAll": { "message": "Sao chép tất cả" }, + "copyToClipboard": { "message": "Sao chép vào clipboard" }, + "activeGmailAddress": { "message": "Địa chỉ Gmail đang dùng" }, + "addNewAccount": { "message": "Thêm tài khoản mới" }, + "addAccount": { "message": "Thêm tài khoản" }, + "addNewAccountTitle": { "message": "Thêm tài khoản mới" }, + "emailPlaceholder": { "message": "email.cua.ban" }, + "accountLabelPlaceholder": { + "message": "Nhãn (tùy chọn, ví dụ: Công việc, Cá nhân)" + }, + "pressTabToAddGmail": { + "message": "Nhấn $KEY$ để thêm @gmail.com", + "placeholders": { "key": { "content": "$1" } } + }, + "gmailWarning": { + "message": "Địa chỉ này có vẻ không phải Gmail. Plus addressing hoạt động tốt nhất với Gmail." + }, + "random": { "message": "Ngẫu nhiên" }, + "customTags": { "message": "Tag tùy chỉnh" }, + "gmailTricks": { "message": "Mẹo Gmail" }, + "tabTagsShort": { "message": "Tag" }, + "tabTricksShort": { "message": "Mẹo" }, + "generating": { "message": "Đang tạo" }, + "copied": { "message": "Đã sao chép" }, + "switchToLightMode": { "message": "Chuyển sang chế độ sáng" }, + "switchToDarkMode": { "message": "Chuyển sang chế độ tối" }, + "aliasColumn": { "message": "Bí danh" }, + "copyEmailTooltip": { + "message": "$EMAIL$ - bấm để sao chép", + "placeholders": { "email": { "content": "$1" } } + }, + "pageLabel": { + "message": "Trang $PAGE$", + "placeholders": { "page": { "content": "$1" } } + }, + "dotTrick": { "message": "Mẹo dấu chấm" }, + "plusTags": { "message": "Tag dấu +" }, + "googlemail": { "message": "Googlemail" }, + "removeDots": { "message": "Bỏ dấu chấm" }, + "dotPlus": { "message": "Chấm + tag" }, + "allCombos": { "message": "Tất cả kiểu" }, + "numberOfVariations": { "message": "Số biến thể" }, + "randomizeDotPositions": { "message": "Ngẫu nhiên vị trí dấu chấm" }, + "sequential": { "message": "Tuần tự" }, + "generateTricks": { "message": "Tạo mẹo Gmail" }, + "generatedVariations": { "message": "Biến thể đã tạo" }, + "gmailTrickInfoLabel": { "message": "Mẹo Gmail:" }, + "gmailTrickInfo": { + "message": "Gmail bỏ qua dấu chấm và mọi phần sau dấu + vẫn vào cùng hộp thư" + }, + "format": { "message": "Định dạng" }, + "privateMailFormat": { "message": "Private Mail (private-mail-xxxx)" }, + "randomCharactersFormat": { "message": "Ký tự ngẫu nhiên (abc123xy)" }, + "randomWordsFormat": { "message": "Từ ngẫu nhiên (happy-fox-42)" }, + "timestampFormat": { "message": "Dấu thời gian (1234567890)" }, + "numberOfAliases": { "message": "Số lượng bí danh" }, + "generateRandomAliases": { + "message": "Tạo $COUNT$ bí danh ngẫu nhiên$PLURAL$", + "placeholders": { + "count": { "content": "$1" }, + "plural": { "content": "$2" } + } + }, + "generatedAliases": { "message": "Bí danh đã tạo" }, + "totalCount": { + "message": "Tổng $COUNT$", + "placeholders": { "count": { "content": "$1" } } + }, + "copiedAliases": { + "message": "Đã sao chép $COUNT$ bí danh!", + "placeholders": { "count": { "content": "$1" } } + }, + "failedToCopy": { "message": "Sao chép thất bại" }, + "formatPrivateMail": { "message": "Định dạng: private-mail-xxxx" }, + "formatAlphanumeric": { "message": "8 ký tự ngẫu nhiên" }, + "formatWords": { "message": "2 từ ngẫu nhiên" }, + "formatTimestamp": { "message": "Dấu thời gian Unix" }, + "tagPlaceholder": { "message": "Nhập tag (ví dụ: shopping, work)" }, + "generate": { "message": "Tạo" }, + "yourPresets": { "message": "Mẫu của bạn" }, + "example": { "message": "Ví dụ:" }, + "copyExample": { "message": "Sao chép ví dụ" }, + "recentAliases": { "message": "Bí danh gần đây" }, + "favorites": { "message": "Yêu thích" }, + "exportAsCsv": { "message": "Xuất CSV" }, + "exportAsJson": { "message": "Xuất JSON" }, + "selectAliases": { "message": "Chọn bí danh" }, + "select": { "message": "Chọn" }, + "starredCount": { + "message": "$COUNT$ đã đánh dấu", + "placeholders": { "count": { "content": "$1" } } + }, + "deselectAll": { "message": "Bỏ chọn tất cả" }, + "selectAll": { "message": "Chọn tất cả" }, + "selectedCount": { + "message": "Đã chọn $COUNT$", + "placeholders": { "count": { "content": "$1" } } + }, + "deleteCount": { + "message": "Xóa $COUNT$", + "placeholders": { "count": { "content": "$1" } } + }, + "allCount": { + "message": "Tất cả ($COUNT$)", + "placeholders": { "count": { "content": "$1" } } + }, + "favoritesCount": { + "message": "Yêu thích ($COUNT$)", + "placeholders": { "count": { "content": "$1" } } + }, + "searchAliases": { "message": "Tìm bí danh..." }, + "allTags": { "message": "Tất cả tag" }, + "mostRecent": { "message": "Mới nhất" }, + "az": { "message": "A-Z" }, + "noFavoritesYet": { "message": "Chưa có mục yêu thích" }, + "starEmailsHint": { + "message": "Đánh dấu sao email trong lịch sử để truy cập nhanh tại đây" + }, + "noResultsFound": { "message": "Không tìm thấy kết quả" }, + "differentSearchHint": { "message": "Thử từ khóa tìm kiếm hoặc bộ lọc khác" }, + "showQrCode": { "message": "Hiển thị mã QR" }, + "removeFromFavorites": { "message": "Bỏ khỏi yêu thích" }, + "addToFavorites": { "message": "Thêm vào yêu thích" }, + "showingRange": { + "message": "Hiển thị $START$-$END$ trên $TOTAL$", + "placeholders": { + "start": { "content": "$1" }, + "end": { "content": "$2" }, + "total": { "content": "$3" } + } + }, + "perPage": { + "message": "$COUNT$ / trang", + "placeholders": { "count": { "content": "$1" } } + }, + "firstPage": { "message": "Trang đầu" }, + "previousPage": { "message": "Trang trước" }, + "nextPage": { "message": "Trang sau" }, + "lastPage": { "message": "Trang cuối" }, + "scanToCopyAlias": { "message": "Quét để sao chép bí danh" }, + "general": { "message": "Chung" }, + "accounts": { "message": "Tài khoản" }, + "changelog": { "message": "Nhật ký thay đổi" }, + "appearanceDisplay": { "message": "Giao diện & hiển thị" }, + "theme": { "message": "Giao diện" }, + "themeLight": { "message": "Sáng" }, + "themeDark": { "message": "Tối" }, + "themeAuto": { "message": "Theo hệ thống" }, + "badgeCounter": { "message": "Bộ đếm huy hiệu" }, + "badgeNone": { "message": "Không hiển thị" }, + "badgeTotal": { "message": "Tổng trong lịch sử" }, + "badgeAllTime": { "message": "Tổng đã tạo" }, + "badgeToday": { "message": "Tạo hôm nay" }, + "badgeWeek": { "message": "Tuần này" }, + "copyNotifications": { "message": "Thông báo khi sao chép" }, + "aliasGeneration": { "message": "Tạo bí danh" }, + "randomAliasFormat": { "message": "Định dạng bí danh ngẫu nhiên" }, + "autoSaveLimit": { "message": "Giới hạn tự lưu" }, + "aliasesLimit": { + "message": "$COUNT$ bí danh", + "placeholders": { "count": { "content": "$1" } } + }, + "customPresets": { "message": "Mẫu tùy chỉnh" }, + "label": { "message": "Nhãn" }, + "tag": { "message": "Tag" }, + "addPreset": { "message": "+ Thêm mẫu" }, + "dataManagement": { "message": "Quản lý dữ liệu" }, + "export": { "message": "Xuất" }, + "import": { "message": "Nhập" }, + "clear": { "message": "Xóa" }, + "resetSettings": { "message": "Khôi phục tất cả cài đặt mặc định" }, + "resetSettingsTitle": { "message": "Khôi phục cài đặt?" }, + "resetSettingsMessage": { + "message": "Thao tác này sẽ đưa mọi cài đặt về giá trị mặc định." + }, + "reset": { "message": "Khôi phục" }, + "clearHistoryTitle": { "message": "Xóa bí danh gần đây?" }, + "clearHistoryMessage": { + "message": "Thao tác này xóa toàn bộ bí danh khỏi lịch sử gần đây." + }, + "emailAccounts": { "message": "Tài khoản email" }, + "manageAccountsDescription": { + "message": "Quản lý các tài khoản Gmail. Mỗi tài khoản có lịch sử, thống kê và yêu thích riêng." + }, + "noAccountsFound": { + "message": "Không tìm thấy tài khoản. Hãy thêm tài khoản từ màn hình chính." + }, + "emailAddress": { "message": "Địa chỉ email" }, + "accountLabel": { "message": "Nhãn tài khoản" }, + "emailAddressPlaceholder": { "message": "email.cua.ban@gmail.com" }, + "emailChangeWarning": { + "message": "Đổi email sẽ chuyển toàn bộ dữ liệu sang địa chỉ email mới" + }, + "saveChanges": { "message": "Lưu thay đổi" }, + "active": { "message": "Đang dùng" }, + "editAccount": { "message": "Sửa tài khoản" }, + "cannotDeleteLastAccountTitle": { + "message": "Không thể xóa tài khoản cuối cùng" + }, + "deleteThisAccount": { "message": "Xóa tài khoản này" }, + "deleteAccountTitle": { "message": "Xóa tài khoản?" }, + "deleteAccountMessage": { + "message": "Xóa \"$LABEL$\" ($EMAIL$)?\n\nThao tác này sẽ xóa vĩnh viễn:\n- Toàn bộ lịch sử của tài khoản này\n- Toàn bộ thống kê\n- Toàn bộ yêu thích\n\nKhông thể hoàn tác.", + "placeholders": { + "label": { "content": "$1" }, + "email": { "content": "$2" } + } + }, + "delete": { "message": "Xóa" }, + "changeAccountEmailTitle": { "message": "Đổi email tài khoản?" }, + "changeAccountEmailMessage": { + "message": "Đổi email từ\n$OLD_EMAIL$\nsang\n$NEW_EMAIL$?\n\nThao tác này sẽ:\n- Chuyển toàn bộ lịch sử, thống kê và yêu thích sang email mới\n- Cập nhật email tài khoản\n- Xóa dữ liệu gắn với email cũ\n\nTiếp tục?", + "placeholders": { + "old_email": { "content": "$1" }, + "new_email": { "content": "$2" } + } + }, + "changeEmail": { "message": "Đổi email" }, + "menuRandomEmailAlias": { "message": "Bí danh email ngẫu nhiên" }, + "menuCustomTags": { "message": "Tag tùy chỉnh" }, + "menuNoPresets": { "message": "Chưa có mẫu - Thêm trong Cài đặt" }, + "menuGmailTricks": { "message": "Mẹo Gmail" }, + "menuDotVariation": { "message": "Biến thể dấu chấm" }, + "menuGooglemailDomain": { "message": "Tên miền Googlemail" }, + "menuRemoveAllDots": { "message": "Xóa toàn bộ dấu chấm" }, + "toastPresetAdded": { "message": "Đã thêm mẫu" }, + "toastPresetRemoved": { "message": "Đã xóa mẫu" }, + "toastSettingsExported": { "message": "Đã xuất cài đặt" }, + "toastSettingsImported": { "message": "Đã nhập cài đặt" }, + "toastImportFailed": { "message": "Nhập thất bại - tệp không hợp lệ" }, + "toastSettingsReset": { "message": "Đã khôi phục cài đặt mặc định" }, + "toastAccountSwitched": { "message": "Đã chuyển tài khoản" }, + "toastAccountDeleted": { "message": "Đã xóa tài khoản" }, + "toastAccountUpdated": { "message": "Đã cập nhật tài khoản" }, + "toastAccountAdded": { + "message": "Đã thêm $LABEL$", + "placeholders": { "label": { "content": "$1" } } + }, + "errorLabelRequired": { "message": "Nhãn không được để trống" }, + "errorInvalidEmail": { "message": "Vui lòng nhập địa chỉ email hợp lệ" }, + "errorDuplicateEmail": { + "message": "Địa chỉ email này đã được dùng bởi tài khoản khác" + }, + "errorEnterEmail": { "message": "Vui lòng nhập địa chỉ email" }, + "errorAccountExists": { "message": "Tài khoản này đã tồn tại" }, + "toastQrFailed": { "message": "Tạo mã QR thất bại" }, + "toastExportedAliases": { + "message": "Đã xuất $COUNT$ bí danh", + "placeholders": { "count": { "content": "$1" } } + }, + "toastDeletedAliases": { + "message": "Đã xóa $COUNT$ bí danh", + "placeholders": { "count": { "content": "$1" } } + }, + "toastHistoryCleared": { "message": "Đã xóa lịch sử" }, + "toastFavoriteAdded": { "message": "Đã thêm vào yêu thích" }, + "toastFavoriteRemoved": { "message": "Đã bỏ khỏi yêu thích" }, + "toastCopiedEmail": { + "message": "Đã sao chép $EMAIL$", + "placeholders": { "email": { "content": "$1" } } + }, + "toastCopyFailed": { "message": "Sao chép thất bại" }, + "welcomeTitle": { "message": "Chào mừng đến với Bộ công cụ bí danh Gmail" }, + "welcomeSubtitle": { + "message": "Tạo bí danh email không giới hạn để bảo vệ riêng tư và sắp xếp hộp thư" + }, + "letsGetStarted": { "message": "Bắt đầu thôi" }, + "enterGmailAddress": { "message": "Nhập địa chỉ Gmail của bạn" }, + "pressTabForGmail": { + "message": "Nhấn $KEY$ để thêm @gmail.com", + "placeholders": { "key": { "content": "$1" } } + }, + "settingUp": { "message": "Đang thiết lập..." }, + "getStarted": { "message": "Bắt đầu" }, + "advancedSetup": { "message": "Thiết lập nâng cao trong Cài đặt" }, + "whatYouCanDo": { "message": "Bạn có thể:" }, + "featurePrivateEmail": { "message": "Tạo email riêng tư" }, + "featureCustomTags": { "message": "Tag tùy chỉnh & mẫu có sẵn" }, + "featureGmailTricks": { "message": "Mẹo Gmail nâng cao" }, + "welcomeFooter": { + "message": "Mọi dữ liệu được lưu cục bộ. Không theo dõi, không máy chủ." + }, + "favoritesSaved": { + "message": "Đã lưu $COUNT$", + "placeholders": { "count": { "content": "$1" } } + }, + "clickStarHint": { + "message": "Bấm sao trên bí danh trong lịch sử để thêm vào đây" + } +} diff --git a/public/_locales/zh_CN/messages.json b/public/_locales/zh_CN/messages.json new file mode 100644 index 0000000..ba6bb74 --- /dev/null +++ b/public/_locales/zh_CN/messages.json @@ -0,0 +1,697 @@ +{ + "extensionName": { + "message": "Gmail Alias Toolkit" + }, + "extensionDescription": { + "message": "使用加号地址和预设生成并管理 Gmail 别名" + }, + "headerSubtitle": { + "message": "使用加号地址生成别名" + }, + "settings": { + "message": "设置" + }, + "back": { + "message": "返回" + }, + "close": { + "message": "关闭" + }, + "cancel": { + "message": "取消" + }, + "copy": { + "message": "复制" + }, + "copyAll": { + "message": "全部复制" + }, + "copyToClipboard": { + "message": "复制到剪贴板" + }, + "activeGmailAddress": { + "message": "当前 Gmail 地址" + }, + "addNewAccount": { + "message": "添加新账户" + }, + "addAccount": { + "message": "添加账户" + }, + "addNewAccountTitle": { + "message": "添加新账户" + }, + "emailPlaceholder": { + "message": "your.email" + }, + "accountLabelPlaceholder": { + "message": "标签(可选,例如工作、个人)" + }, + "pressTabToAddGmail": { + "message": "按 $KEY$ 添加 @gmail.com", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "gmailWarning": { + "message": "这看起来不像 Gmail 地址。加号地址最适合 Gmail。" + }, + "random": { + "message": "随机" + }, + "customTags": { + "message": "自定义标签" + }, + "gmailTricks": { + "message": "Gmail 技巧" + }, + "tabTagsShort": { + "message": "标签" + }, + "tabTricksShort": { + "message": "技巧" + }, + "generating": { + "message": "正在生成" + }, + "copied": { + "message": "已复制" + }, + "switchToLightMode": { + "message": "切换到浅色模式" + }, + "switchToDarkMode": { + "message": "切换到深色模式" + }, + "aliasColumn": { + "message": "别名" + }, + "copyEmailTooltip": { + "message": "$EMAIL$ - 点击复制", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "pageLabel": { + "message": "第 $PAGE$ 页", + "placeholders": { + "page": { + "content": "$1" + } + } + }, + "dotTrick": { + "message": "点号技巧" + }, + "plusTags": { + "message": "加号(+)标签" + }, + "googlemail": { + "message": "Googlemail" + }, + "removeDots": { + "message": "移除点号" + }, + "dotPlus": { + "message": "点号 + 加号" + }, + "allCombos": { + "message": "所有组合" + }, + "numberOfVariations": { + "message": "变体数量" + }, + "randomizeDotPositions": { + "message": "随机点号位置" + }, + "sequential": { + "message": "顺序" + }, + "generateTricks": { + "message": "生成技巧" + }, + "generatedVariations": { + "message": "已生成的变体" + }, + "gmailTrickInfoLabel": { + "message": "Gmail 技巧:" + }, + "gmailTrickInfo": { + "message": "点号会被忽略,+ 后面的所有内容都会进入同一个收件箱" + }, + "format": { + "message": "格式" + }, + "privateMailFormat": { + "message": "Private Mail(private-mail-xxxx)" + }, + "randomCharactersFormat": { + "message": "随机字符(abc123xy)" + }, + "randomWordsFormat": { + "message": "随机单词(happy-fox-42)" + }, + "timestampFormat": { + "message": "时间戳(1234567890)" + }, + "numberOfAliases": { + "message": "别名数量" + }, + "generateRandomAliases": { + "message": "生成 $COUNT$ 个随机别名", + "placeholders": { + "count": { + "content": "$1" + }, + "plural": { + "content": "$2" + } + } + }, + "generatedAliases": { + "message": "已生成的别名" + }, + "totalCount": { + "message": "共 $COUNT$ 个", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "copiedAliases": { + "message": "已复制 $COUNT$ 个别名!", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "failedToCopy": { + "message": "复制失败" + }, + "formatPrivateMail": { + "message": "格式:private-mail-xxxx" + }, + "formatAlphanumeric": { + "message": "8 个随机字符" + }, + "formatWords": { + "message": "2 个随机单词" + }, + "formatTimestamp": { + "message": "Unix 时间戳" + }, + "tagPlaceholder": { + "message": "输入标签(例如 shopping、work)" + }, + "generate": { + "message": "生成" + }, + "yourPresets": { + "message": "你的预设" + }, + "example": { + "message": "示例:" + }, + "copyExample": { + "message": "复制示例" + }, + "recentAliases": { + "message": "最近的别名" + }, + "favorites": { + "message": "收藏" + }, + "exportAsCsv": { + "message": "导出为 CSV" + }, + "exportAsJson": { + "message": "导出为 JSON" + }, + "selectAliases": { + "message": "选择别名" + }, + "select": { + "message": "选择" + }, + "starredCount": { + "message": "$COUNT$ 个已收藏", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deselectAll": { + "message": "取消全选" + }, + "selectAll": { + "message": "全选" + }, + "selectedCount": { + "message": "已选择 $COUNT$ 个", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "deleteCount": { + "message": "删除 $COUNT$ 个", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "allCount": { + "message": "全部($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "favoritesCount": { + "message": "收藏($COUNT$)", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "searchAliases": { + "message": "搜索别名..." + }, + "allTags": { + "message": "所有标签" + }, + "mostRecent": { + "message": "最近" + }, + "az": { + "message": "A-Z" + }, + "noFavoritesYet": { + "message": "还没有收藏" + }, + "starEmailsHint": { + "message": "将历史记录中的邮件加星标,以便在这里快速访问" + }, + "noResultsFound": { + "message": "未找到结果" + }, + "differentSearchHint": { + "message": "尝试其他搜索或筛选条件" + }, + "showQrCode": { + "message": "显示二维码" + }, + "removeFromFavorites": { + "message": "从收藏中移除" + }, + "addToFavorites": { + "message": "添加到收藏" + }, + "showingRange": { + "message": "显示第 $START$-$END$ 项,共 $TOTAL$ 项", + "placeholders": { + "start": { + "content": "$1" + }, + "end": { + "content": "$2" + }, + "total": { + "content": "$3" + } + } + }, + "perPage": { + "message": "$COUNT$ / 页", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "firstPage": { + "message": "第一页" + }, + "previousPage": { + "message": "上一页" + }, + "nextPage": { + "message": "下一页" + }, + "lastPage": { + "message": "最后一页" + }, + "scanToCopyAlias": { + "message": "扫码复制别名" + }, + "general": { + "message": "常规" + }, + "accounts": { + "message": "账户" + }, + "changelog": { + "message": "更新日志" + }, + "appearanceDisplay": { + "message": "外观与显示" + }, + "theme": { + "message": "主题" + }, + "themeLight": { + "message": "浅色" + }, + "themeDark": { + "message": "深色" + }, + "themeAuto": { + "message": "系统(自动)" + }, + "badgeCounter": { + "message": "徽章计数器" + }, + "badgeNone": { + "message": "无(隐藏)" + }, + "badgeTotal": { + "message": "历史记录总数" + }, + "badgeAllTime": { + "message": "总生成数(全部时间)" + }, + "badgeToday": { + "message": "今天创建" + }, + "badgeWeek": { + "message": "本周" + }, + "copyNotifications": { + "message": "复制通知" + }, + "aliasGeneration": { + "message": "别名生成" + }, + "randomAliasFormat": { + "message": "随机别名格式" + }, + "autoSaveLimit": { + "message": "自动保存上限" + }, + "aliasesLimit": { + "message": "$COUNT$ 个别名", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "customPresets": { + "message": "自定义预设" + }, + "label": { + "message": "标签" + }, + "tag": { + "message": "标签" + }, + "addPreset": { + "message": "+ 添加预设" + }, + "dataManagement": { + "message": "数据管理" + }, + "export": { + "message": "导出" + }, + "import": { + "message": "导入" + }, + "clear": { + "message": "清除" + }, + "resetSettings": { + "message": "将所有设置重置为默认值" + }, + "resetSettingsTitle": { + "message": "重置设置?" + }, + "resetSettingsMessage": { + "message": "这会将所有设置恢复为默认值。" + }, + "reset": { + "message": "重置" + }, + "clearHistoryTitle": { + "message": "清除最近的别名?" + }, + "clearHistoryMessage": { + "message": "这会从最近历史列表中移除所有别名。" + }, + "emailAccounts": { + "message": "邮箱账户" + }, + "manageAccountsDescription": { + "message": "管理你的 Gmail 账户。每个账户都有自己的历史记录、统计数据和收藏。" + }, + "noAccountsFound": { + "message": "未找到账户。请从主屏幕添加账户。" + }, + "emailAddress": { + "message": "邮箱地址" + }, + "accountLabel": { + "message": "账户标签" + }, + "emailAddressPlaceholder": { + "message": "your.email@gmail.com" + }, + "emailChangeWarning": { + "message": "更改邮箱会将所有数据迁移到新的邮箱地址" + }, + "saveChanges": { + "message": "保存更改" + }, + "active": { + "message": "当前" + }, + "editAccount": { + "message": "编辑账户" + }, + "cannotDeleteLastAccountTitle": { + "message": "无法删除最后一个账户" + }, + "deleteThisAccount": { + "message": "删除此账户" + }, + "deleteAccountTitle": { + "message": "删除账户?" + }, + "deleteAccountMessage": { + "message": "删除“$LABEL$”($EMAIL$)?\n\n这将永久删除:\n- 此账户的所有历史记录\n- 所有统计数据\n- 所有收藏\n\n此操作无法撤销。", + "placeholders": { + "label": { + "content": "$1" + }, + "email": { + "content": "$2" + } + } + }, + "delete": { + "message": "删除" + }, + "changeAccountEmailTitle": { + "message": "更改账户邮箱?" + }, + "changeAccountEmailMessage": { + "message": "将邮箱从\n$OLD_EMAIL$\n更改为\n$NEW_EMAIL$?\n\n这将:\n- 将所有历史记录、统计数据和收藏迁移到新邮箱\n- 更新账户邮箱\n- 删除与旧邮箱关联的数据\n\n继续?", + "placeholders": { + "old_email": { + "content": "$1" + }, + "new_email": { + "content": "$2" + } + } + }, + "changeEmail": { + "message": "更改邮箱" + }, + "menuRandomEmailAlias": { + "message": "随机邮箱别名" + }, + "menuCustomTags": { + "message": "自定义标签" + }, + "menuNoPresets": { + "message": "没有预设 - 在设置中添加" + }, + "menuGmailTricks": { + "message": "Gmail 技巧" + }, + "menuDotVariation": { + "message": "点号变体" + }, + "menuGooglemailDomain": { + "message": "Googlemail 域名" + }, + "menuRemoveAllDots": { + "message": "移除所有点号" + }, + "toastPresetAdded": { + "message": "已添加预设" + }, + "toastPresetRemoved": { + "message": "已移除预设" + }, + "toastSettingsExported": { + "message": "设置已导出" + }, + "toastSettingsImported": { + "message": "设置已导入" + }, + "toastImportFailed": { + "message": "导入失败 - 文件无效" + }, + "toastSettingsReset": { + "message": "设置已重置为默认值" + }, + "toastAccountSwitched": { + "message": "已切换账户" + }, + "toastAccountDeleted": { + "message": "账户已删除" + }, + "toastAccountUpdated": { + "message": "账户已更新" + }, + "toastAccountAdded": { + "message": "已添加 $LABEL$", + "placeholders": { + "label": { + "content": "$1" + } + } + }, + "errorLabelRequired": { + "message": "标签不能为空" + }, + "errorInvalidEmail": { + "message": "请输入有效的邮箱地址" + }, + "errorDuplicateEmail": { + "message": "此邮箱地址已被另一个账户使用" + }, + "errorEnterEmail": { + "message": "请输入邮箱地址" + }, + "errorAccountExists": { + "message": "此账户已存在" + }, + "toastQrFailed": { + "message": "生成二维码失败" + }, + "toastExportedAliases": { + "message": "已导出 $COUNT$ 个别名", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastDeletedAliases": { + "message": "已删除 $COUNT$ 个别名", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "toastHistoryCleared": { + "message": "历史记录已清除" + }, + "toastFavoriteAdded": { + "message": "已添加到收藏" + }, + "toastFavoriteRemoved": { + "message": "已从收藏中移除" + }, + "toastCopiedEmail": { + "message": "已复制 $EMAIL$", + "placeholders": { + "email": { + "content": "$1" + } + } + }, + "toastCopyFailed": { + "message": "复制失败" + }, + "welcomeTitle": { + "message": "欢迎使用 Gmail Alias Toolkit" + }, + "welcomeSubtitle": { + "message": "生成无限邮箱别名,用于隐私保护和整理" + }, + "letsGetStarted": { + "message": "开始使用" + }, + "enterGmailAddress": { + "message": "输入你的 Gmail 地址" + }, + "pressTabForGmail": { + "message": "按 $KEY$ 输入 @gmail.com", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "settingUp": { + "message": "正在设置..." + }, + "getStarted": { + "message": "开始" + }, + "advancedSetup": { + "message": "在设置中进行高级设置" + }, + "whatYouCanDo": { + "message": "你可以做什么:" + }, + "featurePrivateEmail": { + "message": "私密邮箱生成器" + }, + "featureCustomTags": { + "message": "自定义标签和预设" + }, + "featureGmailTricks": { + "message": "Gmail 高级技巧" + }, + "welcomeFooter": { + "message": "所有数据都存储在本地。无跟踪,无服务器。" + }, + "favoritesSaved": { + "message": "已保存 $COUNT$ 个", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "clickStarHint": { + "message": "点击历史记录中任意别名的星标,即可将其添加到这里" + } +} diff --git a/public/icon/email.svg b/public/icon/email.svg new file mode 100644 index 0000000..73d6640 --- /dev/null +++ b/public/icon/email.svg @@ -0,0 +1,207 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icon/icon-128.png b/public/icon/icon-128.png deleted file mode 100644 index aacd208..0000000 Binary files a/public/icon/icon-128.png and /dev/null differ diff --git a/public/icon/icon-16.png b/public/icon/icon-16.png deleted file mode 100644 index 8d541a2..0000000 Binary files a/public/icon/icon-16.png and /dev/null differ diff --git a/public/icon/icon-256.png b/public/icon/icon-256.png deleted file mode 100644 index ccf5aef..0000000 Binary files a/public/icon/icon-256.png and /dev/null differ diff --git a/public/icon/icon-32.png b/public/icon/icon-32.png deleted file mode 100644 index f999938..0000000 Binary files a/public/icon/icon-32.png and /dev/null differ diff --git a/public/icon/icon-48.png b/public/icon/icon-48.png deleted file mode 100644 index f10bac2..0000000 Binary files a/public/icon/icon-48.png and /dev/null differ diff --git a/public/icon/icon-96.png b/public/icon/icon-96.png deleted file mode 100644 index 1ff5f7b..0000000 Binary files a/public/icon/icon-96.png and /dev/null differ diff --git a/public/wxt.svg b/public/wxt.svg deleted file mode 100644 index 0e76320..0000000 --- a/public/wxt.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/components/alias/AccountSwitcher.tsx b/src/components/alias/AccountSwitcher.tsx new file mode 100644 index 0000000..5b8e689 --- /dev/null +++ b/src/components/alias/AccountSwitcher.tsx @@ -0,0 +1,210 @@ +// skipcq: JS-0415 - Account switcher form layout is intentionally inline and shallow in behavior. +import { Mail, Plus, Tag, UserRound } from "lucide-react"; +import { Button } from "src/components/motion/button/base"; +import { Input } from "src/components/motion/input"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "src/components/motion/select"; +import { Tooltip } from "src/components/motion/tooltip"; +import { t } from "../../../lib/i18n"; + +export interface EmailAccount { + id: string; + email: string; + label?: string; + isActive: boolean; +} +export interface AccountSwitcherProps { + baseEmail: string; + emailAccounts: EmailAccount[]; + showAddAccount: boolean; + newAccountEmail: string; + newAccountLabel: string; + addAccountError: string; + focusOnMount: (el: HTMLInputElement | null) => void; + onToggleAddAccount: () => void; + onSelectAccount: (email: string) => Promise; + onNewAccountEmailChange: (value: string) => void; + onNewAccountLabelChange: (value: string) => void; + onNewAccountBlur: () => void; + onAddAccount: () => void; + onCancelAddAccount: () => void; +} + +interface AccountSelectRowProps { + baseEmail: string; + emailAccounts: EmailAccount[]; + onToggleAddAccount: () => void; + onSelectAccount: (email: string) => Promise; +} + +/** Account select row with quick-add action. */ +function AccountSelectRow({ + baseEmail, + emailAccounts, + onToggleAddAccount, + onSelectAccount, +}: AccountSelectRowProps) { + return ( +
+
+ + +
+ + + +
+ ); +} + +interface QuickAddAccountFormProps { + newAccountEmail: string; + newAccountLabel: string; + addAccountError: string; + focusOnMount: (el: HTMLInputElement | null) => void; + onNewAccountEmailChange: (value: string) => void; + onNewAccountLabelChange: (value: string) => void; + onNewAccountBlur: () => void; + onAddAccount: () => void; + onCancelAddAccount: () => void; +} + +/** Inline account creation form shown below the selector. */ +function QuickAddAccountForm({ + newAccountEmail, + newAccountLabel, + addAccountError, + focusOnMount, + onNewAccountEmailChange, + onNewAccountLabelChange, + onNewAccountBlur, + onAddAccount, + onCancelAddAccount, +}: QuickAddAccountFormProps) { + return ( +
+ } + error={addAccountError || undefined} + ref={focusOnMount} + /> + {newAccountEmail && !newAccountEmail.includes("@") && ( +

+ {t("pressTabToAddGmail", "Tab").split("Tab")[0]} + + Tab + + {t("pressTabToAddGmail", "Tab").split("Tab")[1]} +

+ )} + } + /> +
+ + +
+
+ ); +} + +/** Multi-account selector and quick-add form; delegates all behavior to App. */ +export default function AccountSwitcher(props: AccountSwitcherProps) { + const { + baseEmail, + emailAccounts, + showAddAccount, + newAccountEmail, + newAccountLabel, + addAccountError, + focusOnMount, + onToggleAddAccount, + onSelectAccount, + onNewAccountEmailChange, + onNewAccountLabelChange, + onNewAccountBlur, + onAddAccount, + onCancelAddAccount, + } = props; + return ( +
+ + {showAddAccount && ( + + )} + {baseEmail && + !baseEmail.includes("@gmail.com") && + baseEmail.includes("@") && ( +

+ {t("gmailWarning")} +

+ )} +
+ ); +} diff --git a/src/components/alias/PopupHeader.tsx b/src/components/alias/PopupHeader.tsx new file mode 100644 index 0000000..1a7ab46 --- /dev/null +++ b/src/components/alias/PopupHeader.tsx @@ -0,0 +1,104 @@ +// skipcq: JS-0415 - Header markup is a compact visual composition with no nested business logic. +import { Settings, Sparkles } from "lucide-react"; +import { Button } from "src/components/motion/button/base"; +import { Tooltip } from "src/components/motion/tooltip"; +import { ThemeToggle } from "src/components/motion/theme-toggle"; +import { t } from "../../../lib/i18n"; + +export interface PopupHeaderProps { + onOpenSettings: () => void; + theme: "light" | "dark" | "auto"; + onThemeChange: (theme: "light" | "dark") => void; +} + +/** Brand mark and title copy for the popup header. */ +function HeaderBrand() { + return ( +
+
+ + +
+
+

+ {t("extensionName")} +

+

+ {t("headerSubtitle")} +

+
+
+ ); +} + +interface HeaderActionsProps { + isDark: boolean; + onOpenSettings: () => void; + onThemeChange: (theme: "light" | "dark") => void; +} + +/** Header actions for theme switching and opening settings. */ +function HeaderActions({ + isDark, + onOpenSettings, + onThemeChange, +}: HeaderActionsProps) { + return ( +
+ + + onThemeChange(checked ? "dark" : "light") + } + aria-label={isDark ? t("switchToLightMode") : t("switchToDarkMode")} + variant="circle-blur" + start="top-right" + className="h-9 w-9 rounded-2xl text-primary-foreground transition-colors hover:bg-primary-foreground/15 focus-visible:ring-primary-foreground/60" + iconClassName="h-4 w-4" + /> + + + + +
+ ); +} + +/** beUI Motion header for the extension popup; business logic stays in App. */ +export default function PopupHeader({ + onOpenSettings, + theme, + onThemeChange, +}: PopupHeaderProps) { + const isDark = + theme === "dark" || + (theme === "auto" && + window.matchMedia("(prefers-color-scheme: dark)").matches); + + return ( +
+
+
+ + +
+
+
+ ); +} diff --git a/src/components/motion/action-swap.tsx b/src/components/motion/action-swap.tsx new file mode 100644 index 0000000..23f70df --- /dev/null +++ b/src/components/motion/action-swap.tsx @@ -0,0 +1,385 @@ +"use client"; + +import { + AnimatePresence, + motion, + useReducedMotion, + type HTMLMotionProps, + type Variants, +} from "motion/react"; +import { useLayoutEffect, useRef, useState, type ReactNode } from "react"; +import { + EASE_OUT, + EASE_OUT_CSS, + SPRING_PRESS, + SPRING_SWAP, +} from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +export type ActionSwapItem = { + id: string; + label: ReactNode; + icon?: ReactNode; + ariaLabel?: string; +}; + +export type ActionSwapButtonVariant = + | "primary" + | "secondary" + | "outline" + | "ghost"; +export type ActionSwapButtonSize = "sm" | "md" | "lg" | "icon"; +export type ActionSwapAnimation = "blur" | "roll" | "cascade"; + +/** Animations with a single-element variant set (cascade animates per letter). */ +type CoreAnimation = "blur" | "roll"; + +export interface ActionSwapButtonProps extends Omit< + HTMLMotionProps<"button">, + "children" | "onChange" +> { + items: ActionSwapItem[]; + value?: string; + defaultValue?: string; + onValueChange?: (value: string, item: ActionSwapItem) => void; + variant?: ActionSwapButtonVariant; + size?: ActionSwapButtonSize; + animation?: ActionSwapAnimation; + iconOnly?: boolean; + cycle?: boolean; +} + +export interface ActionSwapTextProps { + value: string; + children: ReactNode; + animation?: ActionSwapAnimation; + className?: string; +} + +export interface ActionSwapIconProps { + value: string; + children: ReactNode; + animation?: ActionSwapAnimation; + className?: string; +} + +const BLUR_TRANSITION = { duration: 0.2, ease: "easeInOut" } as const; +const ROLL_TRANSITION = { duration: 0.24, ease: EASE_OUT } as const; +const SWAP_BLUR = "blur(8px)"; +const ROLL_BLUR = "blur(6px)"; + +// Cascade rolls the label one letter at a time, left to right. The leaving +// and landing strings overlap as independent layers (no shared cells), so +// proportional glyph widths never jitter. Exits cascade at half the enter +// stagger so the tail of the old label lingers briefly. +const CASCADE_STAGGER = 0.025; + +const CASCADE_LETTER_VARIANTS: Variants = { + initial: { opacity: 0, y: "105%", filter: ROLL_BLUR }, + animate: (delay = 0) => ({ + opacity: 1, + y: "0%", + filter: "blur(0px)", + transition: { ...SPRING_SWAP, delay }, + }), + exit: (delay = 0) => ({ + opacity: 0, + y: "-105%", + filter: ROLL_BLUR, + transition: { duration: 0.16, ease: EASE_OUT, delay: delay * 0.5 }, + }), +}; + +const TEXT_VARIANTS: Record = { + blur: { + initial: { opacity: 0, scale: 0.94, filter: SWAP_BLUR }, + animate: { + opacity: 1, + scale: 1, + filter: "blur(0px)", + transition: BLUR_TRANSITION, + }, + exit: { + opacity: 0, + scale: 0.94, + filter: SWAP_BLUR, + transition: BLUR_TRANSITION, + }, + }, + roll: { + initial: { opacity: 0, y: "115%", filter: ROLL_BLUR }, + animate: { + opacity: 1, + y: "0%", + filter: "blur(0px)", + transition: ROLL_TRANSITION, + }, + exit: { + opacity: 0, + y: "-115%", + filter: ROLL_BLUR, + transition: { duration: 0.18, ease: "easeInOut" }, + }, + }, +}; + +const ICON_VARIANTS: Record = { + blur: { + initial: { opacity: 0, scale: 0.25, filter: SWAP_BLUR }, + animate: { + opacity: 1, + scale: 1, + filter: "blur(0px)", + transition: BLUR_TRANSITION, + }, + exit: { + opacity: 0, + scale: 0.25, + filter: SWAP_BLUR, + transition: BLUR_TRANSITION, + }, + }, + roll: { + initial: { opacity: 0, y: 16, filter: ROLL_BLUR }, + animate: { + opacity: 1, + y: 0, + filter: "blur(0px)", + transition: ROLL_TRANSITION, + }, + exit: { + opacity: 0, + y: -16, + filter: ROLL_BLUR, + transition: { duration: 0.18, ease: "easeInOut" }, + }, + }, +}; + +const VARIANT_CLASS: Record = { + primary: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "border border-border bg-card text-foreground hover:border-border", + outline: + "border border-border bg-transparent text-foreground hover:bg-primary/5", + ghost: "text-muted-foreground hover:bg-primary/5 hover:text-foreground", +}; + +const SIZE_CLASS: Record = { + sm: "h-8 gap-1.5 rounded-full px-3 text-xs", + md: "h-10 gap-2 rounded-full px-4 text-sm", + lg: "h-12 gap-2.5 rounded-full px-5 text-base", + icon: "h-10 w-10 rounded-full", +}; + +/** Animates text when the active action value changes. */ +export function ActionSwapText({ + value, + children, + animation = "blur", + className, +}: ActionSwapTextProps) { + const reduce = useReducedMotion(); + const measureRef = useRef(null); + const [width, setWidth] = useState(); + + useLayoutEffect(() => { + const nextWidth = measureRef.current?.offsetWidth; + if (!nextWidth) return; + setWidth((currentWidth) => + currentWidth === nextWidth ? currentWidth : nextWidth, + ); + }); + + // Cascade needs a plain string to split into letters; non-string content + // and reduced motion fall back to the closest single-element animation. + const label = typeof children === "string" ? children : null; + const cascade = animation === "cascade" && label !== null && !reduce; + const coreAnimation: CoreAnimation = + animation === "cascade" ? "roll" : animation; + + return ( + + + {children} + + {cascade ? ( + <> + {/* Letters are decorative fragments; readers get the whole label. */} + {label} + + + {label.split("").map((char, index) => ( + + {char} + + ))} + + + + ) : ( + + + {children} + + + )} + + ); +} + +/** Animates an icon when the active action value changes. */ +export function ActionSwapIcon({ + value, + children, + animation = "blur", + className, +}: ActionSwapIconProps) { + const reduce = useReducedMotion(); + // Icons are single elements — cascade maps to its closest motion, roll. + const coreAnimation: CoreAnimation = + animation === "cascade" ? "roll" : animation; + + return ( + + + + {children} + + + + ); +} + +/** Button that swaps its label and icon between a small set of states. */ +export function ActionSwapButton({ + items, + value, + defaultValue, + onValueChange, + variant = "secondary", + size = "md", + animation = "blur", + iconOnly = size === "icon", + cycle = true, + className, + disabled, + onClick, + ...rest +}: ActionSwapButtonProps) { + const reduce = useReducedMotion(); + const [internalValue, setInternalValue] = useState( + defaultValue ?? items[0]?.id, + ); + const currentValue = value ?? internalValue; + const activeIndex = Math.max( + 0, + items.findIndex((item) => item.id === currentValue), + ); + const activeItem = items[activeIndex] ?? items[0]; + const hasIcon = items.some((item) => item.icon); + const nextItem = + cycle && items.length > 0 + ? items[(activeIndex + 1) % items.length] + : undefined; + + if (!activeItem) return null; + + const accessibleLabel = + activeItem.ariaLabel ?? + (iconOnly && typeof activeItem.label === "string" + ? activeItem.label + : undefined); + + return ( + { + onClick?.(event); + if (event.defaultPrevented || disabled || !cycle || !nextItem) return; + if (value === undefined) setInternalValue(nextItem.id); + onValueChange?.(nextItem.id, nextItem); + }} + {...rest} + > + {hasIcon ? ( + + {activeItem.icon ?? null} + + ) : null} + {!iconOnly ? ( + + {activeItem.label} + + ) : null} + + ); +} diff --git a/src/components/motion/animated-badge.tsx b/src/components/motion/animated-badge.tsx new file mode 100644 index 0000000..955cb4d --- /dev/null +++ b/src/components/motion/animated-badge.tsx @@ -0,0 +1,216 @@ +"use client"; +// beui.dev/components/motion/animated-badge + +import { + AlertTriangle, + Check, + Circle, + Info, + LoaderCircle, + X, + type LucideIcon, +} from "lucide-react"; +import { + AnimatePresence, + motion, + useReducedMotion, + type HTMLMotionProps, + type Variants, +} from "motion/react"; +import type { ReactNode } from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +export type AnimatedBadgeStatus = + | "neutral" + | "info" + | "success" + | "warning" + | "danger" + | "loading"; + +export type AnimatedBadgeSize = "sm" | "md"; + +export interface AnimatedBadgeProps extends Omit< + HTMLMotionProps<"span">, + "children" +> { + status?: AnimatedBadgeStatus; + size?: AnimatedBadgeSize; + children?: ReactNode; + icon?: ReactNode; + showIcon?: boolean; + pulse?: boolean; + contentKey?: string | number; +} + +const STATUS_CLASS: Record = { + neutral: "border-border bg-card text-muted-foreground", + info: "border-primary/30 bg-primary/10 text-primary", + success: + "border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400", + warning: + "border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400", + danger: "border-destructive/30 bg-destructive/10 text-destructive", + loading: "border-primary/30 bg-primary/10 text-primary", +}; + +const SIZE_CLASS: Record = { + sm: "h-6 gap-1.5 px-2 text-[11px]", + md: "h-8 gap-2 px-3 text-xs", +}; + +const ICON_CLASS: Record = { + sm: "h-3 w-3", + md: "h-3.5 w-3.5", +}; + +const ICONS: Record = { + neutral: Circle, + info: Info, + success: Check, + warning: AlertTriangle, + danger: X, + loading: LoaderCircle, +}; + +const ICON_ROLL_VARIANTS: Variants = { + initial: { + opacity: 0.72, + y: "80%", + scale: 0.92, + rotate: -8, + filter: "blur(6px)", + }, + animate: { + opacity: 1, + y: "0%", + scale: 1, + rotate: 0, + filter: "blur(0px)", + transition: { + y: { type: "spring", stiffness: 210, damping: 24, mass: 0.85 }, + scale: { type: "spring", stiffness: 250, damping: 24, mass: 0.75 }, + rotate: { duration: 0.28, ease: EASE_OUT }, + opacity: { duration: 0.28, ease: EASE_OUT }, + filter: { duration: 0.42, ease: EASE_OUT }, + }, + }, + exit: { + opacity: 0.5, + y: "-80%", + scale: 0.96, + rotate: 8, + filter: "blur(6px)", + transition: { duration: 0.22, ease: EASE_OUT }, + }, +}; + +const TEXT_ROLL_VARIANTS: Variants = { + initial: { opacity: 0.76, y: "85%", filter: "blur(6px)" }, + animate: { + opacity: 1, + y: "0%", + filter: "blur(0px)", + transition: { + y: { type: "spring", stiffness: 210, damping: 24, mass: 0.85 }, + opacity: { duration: 0.3, ease: EASE_OUT }, + filter: { duration: 0.42, ease: EASE_OUT }, + }, + }, + exit: { + opacity: 0.5, + y: "-85%", + filter: "blur(6px)", + transition: { duration: 0.2, ease: EASE_OUT }, + }, +}; + +export function AnimatedBadge({ + status = "neutral", + size = "md", + children, + icon, + showIcon = true, + pulse = status === "loading", + contentKey, + className, + ...rest +}: AnimatedBadgeProps) { + const reduce = useReducedMotion(); + const Icon = ICONS[status]; + const resolvedContentKey = + contentKey ?? + (typeof children === "string" || typeof children === "number" + ? children + : status); + + return ( + + {pulse && !reduce ? ( + + ) : null} + {showIcon ? ( + + + + {status === "loading" && !reduce && !icon ? ( + + + + ) : ( + (icon ?? ) + )} + + + + ) : null} + {children != null ? ( + + + + {children} + + + + ) : null} + + ); +} diff --git a/src/components/motion/animated-number.tsx b/src/components/motion/animated-number.tsx new file mode 100644 index 0000000..9b6b3c7 --- /dev/null +++ b/src/components/motion/animated-number.tsx @@ -0,0 +1,51 @@ +"use client"; +// beui.dev/components/motion/number + +import { animate, useInView, useReducedMotion } from "motion/react"; +import { useEffect, useRef, useState } from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +export interface AnimatedNumberProps { + value: number; + duration?: number; + format?: (n: number) => string; + className?: string; + startOnView?: boolean; +} + +export function AnimatedNumber({ + value, + duration = 1.2, + format = (n) => Math.round(n).toLocaleString(), + className, + startOnView = true, +}: AnimatedNumberProps) { + const ref = useRef(null); + const inView = useInView(ref, { once: true, amount: 0.6 }); + const reduce = useReducedMotion(); + const [display, setDisplay] = useState(0); + const fromRef = useRef(0); + + useEffect(() => { + if (startOnView && !inView) return; + if (reduce) { + fromRef.current = value; + setDisplay(value); + return; + } + const controls = animate(fromRef.current, value, { + duration, + ease: EASE_OUT, + onUpdate: (v) => setDisplay(v), + }); + fromRef.current = value; + return () => controls.stop(); + }, [value, duration, inView, startOnView, reduce]); + + return ( + + {format(display)} + + ); +} diff --git a/src/components/motion/animated-toast-stack.tsx b/src/components/motion/animated-toast-stack.tsx new file mode 100644 index 0000000..0b48fa7 --- /dev/null +++ b/src/components/motion/animated-toast-stack.tsx @@ -0,0 +1,514 @@ +"use client"; +// beui.dev/components/motion/animated-toast-stack + +import { + AlertCircle, + Bell, + Check, + Info, + LoaderCircle, + X, + type LucideIcon, +} from "lucide-react"; +import { + AnimatePresence, + motion, + useReducedMotion, + type Transition, +} from "motion/react"; +import { + memo, + useCallback, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, +} from "react"; +import { createPortal } from "react-dom"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +export type ToastStatus = "neutral" | "info" | "loading" | "success" | "error"; +export type ToastPosition = + | "top-left" + | "top-center" + | "top-right" + | "bottom-left" + | "bottom-center" + | "bottom-right"; + +export type AnimatedToastAction = { + label: ReactNode; + onClick: (toast: AnimatedToast) => void; +}; + +export type AnimatedToast = { + id: string; + title: ReactNode; + description?: ReactNode; + status?: ToastStatus; + icon?: ReactNode; + action?: AnimatedToastAction; + duration?: number; + dismissible?: boolean; + createdAt?: number; +}; + +export type ToastInput = Omit & { + id?: string; +}; + +export type ToastClassNames = { + root?: string; + item?: string; + surface?: string; + iconWrap?: string; + content?: string; + title?: string; + description?: string; + action?: string; + close?: string; + progress?: string; +}; + +export interface AnimatedToastStackProps { + toasts: AnimatedToast[]; + onDismiss?: (id: string) => void; + position?: ToastPosition; + placement?: "static" | "fixed" | "absolute"; + fixed?: boolean; + portal?: boolean; + portalRoot?: Element | null; + maxVisible?: number; + className?: string; + classNames?: ToastClassNames; + icons?: Partial>; + renderToast?: (toast: AnimatedToast) => ReactNode; +} + +export interface UseAnimatedToastStackOptions { + initialToasts?: ToastInput[]; + defaultDuration?: number; + limit?: number; +} + +const STACK_SPRING: Transition = { + type: "spring", + stiffness: 420, + damping: 34, + mass: 0.75, +}; + +const CONTENT_TRANSITION = { + duration: 0.28, + ease: EASE_OUT, +} as const; + +const STATUS_ICON: Record = { + neutral: Bell, + info: Info, + loading: LoaderCircle, + success: Check, + error: AlertCircle, +}; + +const STATUS_CLASS: Record = { + neutral: "text-muted-foreground bg-primary/[0.05]", + info: "text-primary bg-primary/10", + loading: "text-primary bg-primary/10", + success: "text-emerald-600 bg-emerald-500/10 dark:text-emerald-400", + error: "text-destructive bg-destructive/10", +}; + +const POSITION_CLASS: Record = { + "top-left": "left-4 top-4", + "top-center": "left-1/2 top-4 -translate-x-1/2", + "top-right": "right-4 top-4", + "bottom-left": "bottom-6 left-4", + "bottom-center": "bottom-6 left-1/2 -translate-x-1/2", + "bottom-right": "bottom-6 right-4", +}; + +let idSeed = 0; + +function createToast( + input: ToastInput, + defaultDuration: number, +): AnimatedToast { + return { + duration: defaultDuration, + dismissible: true, + ...input, + id: input.id ?? `toast-${Date.now()}-${idSeed++}`, + createdAt: Date.now(), + }; +} + +export function useAnimatedToastStack({ + initialToasts = [], + defaultDuration = 4200, + limit, +}: UseAnimatedToastStackOptions = {}) { + const toastTimers = useRef>( + new Map(), + ); + const [toasts, setToasts] = useState(() => + initialToasts.map((toast) => createToast(toast, defaultDuration)), + ); + + const dismissToast = useCallback((id: string) => { + setToasts((current) => current.filter((toast) => toast.id !== id)); + }, []); + + const clearToasts = useCallback(() => { + setToasts([]); + }, []); + + const showToast = useCallback( + (input: ToastInput) => { + const toast = createToast(input, defaultDuration); + setToasts((current) => { + const next = [...current, toast]; + return typeof limit === "number" ? next.slice(-limit) : next; + }); + return toast.id; + }, + [defaultDuration, limit], + ); + + const updateToast = useCallback((id: string, patch: Partial) => { + setToasts((current) => + current.map((toast) => + toast.id === id + ? { + ...toast, + ...patch, + id, + createdAt: + patch.duration === undefined ? toast.createdAt : Date.now(), + } + : toast, + ), + ); + }, []); + + useEffect(() => { + const activeIds = new Set(toasts.map((toast) => toast.id)); + + toastTimers.current.forEach((entry, id) => { + if (!activeIds.has(id)) { + window.clearTimeout(entry.timer); + toastTimers.current.delete(id); + } + }); + + toasts.forEach((toast) => { + const duration = toast.duration ?? defaultDuration; + const existing = toastTimers.current.get(toast.id); + + if (duration <= 0) { + if (existing) { + window.clearTimeout(existing.timer); + toastTimers.current.delete(toast.id); + } + return; + } + + const createdAt = toast.createdAt ?? Date.now(); + const signature = `${createdAt}:${duration}`; + + if (existing?.signature === signature) { + return; + } + + if (existing) { + window.clearTimeout(existing.timer); + } + + const elapsed = Date.now() - createdAt; + const remaining = Math.max(duration - elapsed, 0); + const timer = window.setTimeout(() => { + toastTimers.current.delete(toast.id); + dismissToast(toast.id); + }, remaining); + + toastTimers.current.set(toast.id, { timer, signature }); + }); + }, [defaultDuration, dismissToast, toasts]); + + useEffect(() => { + const timers = toastTimers.current; + + return () => { + timers.forEach((entry) => { + window.clearTimeout(entry.timer); + }); + timers.clear(); + }; + }, []); + + return useMemo( + () => ({ + toasts, + showToast, + updateToast, + dismissToast, + clearToasts, + setToasts, + }), + [clearToasts, dismissToast, showToast, toasts, updateToast], + ); +} + +export function AnimatedToastStack({ + toasts, + onDismiss, + position = "bottom-right", + placement, + fixed = false, + portal, + portalRoot, + maxVisible = 4, + className, + classNames, + icons, + renderToast, +}: AnimatedToastStackProps) { + const [mounted, setMounted] = useState(false); + const visibleToasts = toasts.slice(-maxVisible); + const isBottom = position.startsWith("bottom"); + const resolvedPlacement = placement ?? (fixed ? "fixed" : "static"); + const shouldPortal = portal ?? resolvedPlacement === "fixed"; + + useEffect(() => { + setMounted(true); + }, []); + + const stack = ( +
    + + {visibleToasts.map((toast, index) => ( + + ))} + +
+ ); + + if (shouldPortal && !mounted) { + return null; + } + + if (shouldPortal) { + return createPortal(stack, portalRoot ?? document.body); + } + + return stack; +} + +const ToastItem = memo(function ToastItem({ + toast, + index, + onDismiss, + classNames, + icons, + renderToast, +}: { + toast: AnimatedToast; + index: number; + onDismiss?: (id: string) => void; + classNames?: ToastClassNames; + icons?: Partial>; + renderToast?: (toast: AnimatedToast) => ReactNode; +}) { + const reduce = useReducedMotion(); + const status = toast.status ?? "neutral"; + const Icon = STATUS_ICON[status]; + const iconNode = icons?.[status] ?? toast.icon ?? ( + + ); + const canDismiss = toast.dismissible !== false && Boolean(onDismiss); + + return ( + { + if (!canDismiss || !onDismiss) return; + if (Math.abs(info.offset.x) > 72 || Math.abs(info.velocity.x) > 520) { + onDismiss(toast.id); + } + }} + className={cn( + "pointer-events-auto relative will-change-transform", + classNames?.item, + )} + style={{ zIndex: 20 - index }} + > +
+ {renderToast ? ( + renderToast(toast) + ) : ( +
+ + + + {status === "loading" ? ( + {iconNode} + ) : ( + iconNode + )} + + + + +
+ + +

+ {toast.title} +

+ {toast.description ? ( +

+ {toast.description} +

+ ) : null} +
+
+ + {toast.action ? ( + + ) : null} +
+ + {canDismiss ? ( + + ) : null} +
+ )} +
+
+ ); +}); diff --git a/src/components/motion/bouncy-accordion.tsx b/src/components/motion/bouncy-accordion.tsx new file mode 100644 index 0000000..a2767a5 --- /dev/null +++ b/src/components/motion/bouncy-accordion.tsx @@ -0,0 +1,320 @@ +"use client"; +// beui.dev/components/motion/bouncy-accordion + +import { motion, useReducedMotion, type Transition } from "motion/react"; +import { ChevronDown } from "lucide-react"; +import { + useCallback, + useId, + useLayoutEffect, + useRef, + useState, + type ReactNode, +} from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +export type BouncyAccordionItem = { + id: string; + title: ReactNode; + description?: ReactNode; + icon?: ReactNode; + disabled?: boolean; +}; + +export type BouncyAccordionClassNames = { + root?: string; + item?: string; + trigger?: string; + icon?: string; + title?: string; + chevron?: string; + content?: string; + description?: string; +}; + +export interface BouncyAccordionProps { + items: BouncyAccordionItem[]; + value?: string | null; + defaultValue?: string | null; + onValueChange?: (value: string | null) => void; + collapsible?: boolean; + className?: string; + classNames?: BouncyAccordionClassNames; +} + +// Local springs keep the accordion's connected groups moving together while +// avoiding scale projection on text-heavy row contents. +// Gap spring: must not overshoot y — positive y overshoot drifts items below +// their mt-3 resting point and briefly overlaps the next item. +const ROW_TRANSITION: Transition = { + type: "spring", + duration: 0.55, + bounce: 0.38, +}; + +const CONTENT_OPEN_TRANSITION: Transition = { + type: "spring", + duration: 0.58, + bounce: 0.32, +}; + +const CONTENT_CLOSE_TRANSITION: Transition = { + type: "spring", + duration: 0.46, + bounce: 0.26, +}; + +const DESCRIPTION_TRANSITION: Transition = { + duration: 0.18, + ease: EASE_OUT, +}; + +const CHEVRON_TRANSITION: Transition = { + type: "spring", + duration: 0.42, + bounce: 0.28, +}; + +function useControllableAccordionValue({ + value, + defaultValue, + onValueChange, +}: { + value?: string | null; + defaultValue?: string | null; + onValueChange?: (value: string | null) => void; +}) { + const [internalValue, setInternalValue] = useState(defaultValue ?? null); + const isControlled = value !== undefined; + const currentValue = value ?? internalValue; + + const setValue = useCallback( + (next: string | null) => { + if (!isControlled) { + setInternalValue(next); + } + + onValueChange?.(next); + }, + [isControlled, onValueChange], + ); + + return [currentValue, setValue] as const; +} + +function BouncyAccordionRow({ + item, + open, + startsGroup, + endsGroup, + separatedFromPrevious, + contentId, + triggerId, + reduce, + classNames, + onToggle, +}: { + item: BouncyAccordionItem; + open: boolean; + startsGroup: boolean; + endsGroup: boolean; + separatedFromPrevious: boolean; + contentId: string; + triggerId: string; + reduce: boolean | null; + classNames?: BouncyAccordionClassNames; + onToggle: () => void; +}) { + const contentRef = useRef(null); + const [contentHeight, setContentHeight] = useState(0); + + useLayoutEffect(() => { + const node = contentRef.current; + if (!node) return; + + const updateHeight = () => { + setContentHeight(node.offsetHeight); + }; + + updateHeight(); + + const observer = new ResizeObserver(updateHeight); + observer.observe(node); + + return () => { + observer.disconnect(); + }; + }, []); + + return ( + + + + + + +
+ {item.description} +
+
+
+
+
+ ); +} + +export function BouncyAccordion({ + items, + value, + defaultValue = null, + onValueChange, + collapsible = true, + className, + classNames, +}: BouncyAccordionProps) { + const reduce = useReducedMotion(); + const baseId = useId(); + const [activeValue, setActiveValue] = useControllableAccordionValue({ + value, + defaultValue, + onValueChange, + }); + const activeIndex = items.findIndex((item) => item.id === activeValue); + + const toggleItem = useCallback( + (id: string) => { + if (activeValue === id) { + if (collapsible) { + setActiveValue(null); + } + return; + } + + setActiveValue(id); + }, + [activeValue, collapsible, setActiveValue], + ); + + return ( +
+ {items.map((item, index) => { + const open = activeValue === item.id; + const previousIsOpen = activeIndex === index - 1; + const nextIsOpen = activeIndex === index + 1; + const startsGroup = open || index === 0 || previousIsOpen; + const endsGroup = open || index === items.length - 1 || nextIsOpen; + const separatedFromPrevious = index > 0 && (open || previousIsOpen); + const contentId = `${baseId}-${item.id}-content`; + const triggerId = `${baseId}-${item.id}-trigger`; + + return ( + toggleItem(item.id)} + /> + ); + })} +
+ ); +} diff --git a/src/components/motion/button/base.tsx b/src/components/motion/button/base.tsx new file mode 100644 index 0000000..cf05162 --- /dev/null +++ b/src/components/motion/button/base.tsx @@ -0,0 +1,165 @@ +"use client"; + +import { + AnimatePresence, + motion, + useReducedMotion, + type HTMLMotionProps, +} from "motion/react"; +import { + forwardRef, + type PointerEvent, + type ReactNode, + useCallback, + useRef, + useState, +} from "react"; +import { EASE_OUT, SPRING_PRESS } from "src/lib/ease"; +import { cn } from "src/lib/utils"; +import { useHoverCapable } from "src/lib/hooks/use-hover-capable"; + +export type ButtonVariant = + | "primary" + | "secondary" + | "ghost" + | "outline" + | "danger" + | "success"; +export type ButtonSize = "sm" | "md" | "lg" | "icon"; + +export interface ButtonProps extends Omit< + HTMLMotionProps<"button">, + "children" +> { + variant?: ButtonVariant; + size?: ButtonSize; + pressScale?: number; + /** Spawn a Material-style ripple from the press point. Off by default. */ + ripple?: boolean; + fullWidth?: boolean; + icon?: ReactNode; + children?: ReactNode; +} + +type Ripple = { id: number; x: number; y: number; size: number }; + +const VARIANT_CLASS: Record = { + primary: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "border border-border bg-card text-foreground hover:border-border", + ghost: "text-muted-foreground hover:text-foreground hover:bg-primary/5", + outline: + "border border-border bg-transparent text-foreground hover:bg-primary/5", + danger: + "bg-destructive/10 text-destructive hover:bg-destructive/20 dark:hover:bg-destructive/15", + success: + "bg-accent/10 text-accent hover:bg-accent/20 dark:hover:bg-accent/15", +}; + +const SIZE_CLASS: Record = { + sm: "h-8 px-3 text-xs gap-1.5 rounded-full", + md: "h-10 px-5 text-sm gap-2 rounded-full", + lg: "h-12 px-6 text-base gap-2 rounded-full", + icon: "h-8 w-8 rounded-lg", +}; + +export const Button = forwardRef( + function Button( + { + variant = "primary", + size = "md", + pressScale = 0.93, + ripple = false, + fullWidth = false, + icon, + disabled, + className, + children, + onPointerDown, + ...rest + }, + ref, + ) { + const reduce = useReducedMotion(); + const canHover = useHoverCapable(); + const [ripples, setRipples] = useState([]); + const nextId = useRef(0); + + const handlePointerDown = useCallback( + (event: PointerEvent) => { + if (ripple && !reduce) { + const rect = event.currentTarget.getBoundingClientRect(); + const size = Math.max(rect.width, rect.height) * 2; + setRipples((prev) => [ + ...prev, + { + id: nextId.current++, + x: event.clientX - rect.left, + y: event.clientY - rect.top, + size, + }, + ]); + } + onPointerDown?.(event); + }, + [ripple, reduce, onPointerDown], + ); + + return ( + + {ripple && !reduce ? ( + + + {ripples.map((r) => ( + + setRipples((prev) => prev.filter((x) => x.id !== r.id)) + } + /> + ))} + + + ) : null} + {icon ? ( + + {icon} + + ) : null} + {children} + + ); + }, +); diff --git a/src/components/motion/button/magnetic.tsx b/src/components/motion/button/magnetic.tsx new file mode 100644 index 0000000..9dc2fc0 --- /dev/null +++ b/src/components/motion/button/magnetic.tsx @@ -0,0 +1,29 @@ +"use client"; +// beui.dev/components/motion/button + +import { forwardRef } from "react"; +import { Magnetic } from "../magnetic"; +import { Button, type ButtonProps } from "./base"; + +export interface MagneticButtonProps extends ButtonProps { + /** Magnetic pull strength. Default 0.25. */ + strength?: number; + /** Class applied to the magnetic wrapper. */ + magneticClassName?: string; +} + +export const MagneticButton = forwardRef< + HTMLButtonElement, + MagneticButtonProps +>(function MagneticButton( + { strength = 0.25, magneticClassName, children, ...rest }, + ref, +) { + return ( + + + + ); +}); diff --git a/src/components/motion/button/stateful.tsx b/src/components/motion/button/stateful.tsx new file mode 100644 index 0000000..15da67f --- /dev/null +++ b/src/components/motion/button/stateful.tsx @@ -0,0 +1,241 @@ +"use client"; +// beui.dev/components/motion/button + +import { + AnimatePresence, + motion, + useReducedMotion, + type Variants, +} from "motion/react"; +import { Check, Loader2, X } from "lucide-react"; +import { + forwardRef, + useLayoutEffect, + useRef, + useState, + type ReactNode, +} from "react"; +import { EASE_OUT, SPRING_SWAP } from "src/lib/ease"; +import { Button, type ButtonProps } from "./base"; + +export type ButtonState = "idle" | "loading" | "success" | "error"; + +export interface StatefulButtonProps extends Omit { + state?: ButtonState; + children: ReactNode; + loadingText?: ReactNode; + successText?: ReactNode; + errorText?: ReactNode; + icon?: ReactNode; +} + +const CASCADE_STAGGER = 0.025; +const ROLL_BLUR = "blur(6px)"; + +const CASCADE_LETTER_VARIANTS: Variants = { + initial: { opacity: 0, y: "105%", filter: ROLL_BLUR }, + animate: (delay = 0) => ({ + opacity: 1, + y: "0%", + filter: "blur(0px)", + transition: { ...SPRING_SWAP, delay }, + }), + exit: (delay = 0) => ({ + opacity: 0, + y: "-105%", + filter: ROLL_BLUR, + transition: { duration: 0.16, ease: EASE_OUT, delay: delay * 0.5 }, + }), +}; + +const ICON_VARIANTS: Variants = { + // Width collapses too, so the icon adds/removes its own space smoothly + // instead of popping the row width in a single frame. + initial: { opacity: 0, width: 0, scale: 0.7, filter: ROLL_BLUR }, + animate: { + opacity: 1, + width: "1.5rem", + scale: 1, + filter: "blur(0px)", + transition: SPRING_SWAP, + }, + exit: { + opacity: 0, + width: 0, + scale: 0.7, + filter: ROLL_BLUR, + transition: { duration: 0.16, ease: EASE_OUT }, + }, +}; + +/** Animated slot for the status icon in a stateful button. */ +function IconSlot({ keyId, children }: { keyId: string; children: ReactNode }) { + const reduce = useReducedMotion(); + return ( + + {children} + + ); +} + +/** Animated slot for the stateful button label. */ +function TextSlot({ value, children }: { value: string; children: ReactNode }) { + const reduce = useReducedMotion(); + const measureRef = useRef(null); + const [width, setWidth] = useState(); + const label = typeof children === "string" ? children : null; + const cascade = label !== null && !reduce; + + // Width is set instantly from the measurer; the parent's single `layout` + // animation smooths the resize (text + icons together) so nothing competes. + useLayoutEffect(() => { + const nextWidth = measureRef.current?.offsetWidth; + if (!nextWidth) return; + setWidth((current) => (current === nextWidth ? current : nextWidth)); + }); + + return ( + + + {children} + + + {cascade ? ( + <> + {label} + + + {label.split("").map((char, index) => ( + + {char} + + ))} + + + + ) : ( + + + {children} + + + )} + + ); +} + +export const StatefulButton = forwardRef< + HTMLButtonElement, + StatefulButtonProps +>(function StatefulButton( + { + state = "idle", + children, + loadingText = "Loading", + successText = "Done", + errorText = "Try again", + icon, + disabled, + ...rest + }, + ref, +) { + const isBusy = state === "loading"; + const stateText = + state === "loading" + ? loadingText + : state === "success" + ? successText + : state === "error" + ? errorText + : children; + const textKey = + typeof stateText === "string" ? `${state}-${stateText}` : state; + + return ( + + ); +}); diff --git a/src/components/motion/checkbox.tsx b/src/components/motion/checkbox.tsx new file mode 100644 index 0000000..b0ceff4 --- /dev/null +++ b/src/components/motion/checkbox.tsx @@ -0,0 +1,125 @@ +"use client"; +// beui.dev/components/motion/checkbox + +import { AnimatePresence, motion, useReducedMotion } from "motion/react"; +import { useId } from "react"; +import { EASE_OUT, SPRING_PRESS } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +const CHECK_PATH = "M5 13l4 4L19 7"; +const INDETERMINATE_PATH = "M6 12h12"; + +export interface CheckboxProps { + checked: boolean; + onCheckedChange: (checked: boolean) => void; + disabled?: boolean; + indeterminate?: boolean; + label?: string; + className?: string; + id?: string; + "aria-label"?: string; +} + +export function Checkbox({ + checked, + onCheckedChange, + disabled, + indeterminate, + label, + className, + id: idProp, + "aria-label": ariaLabel, +}: CheckboxProps) { + const autoId = useId(); + const id = idProp ?? autoId; + const reduce = useReducedMotion(); + const showMark = checked || indeterminate; + const path = indeterminate ? INDETERMINATE_PATH : CHECK_PATH; + + return ( + + ); +} diff --git a/src/components/motion/input.tsx b/src/components/motion/input.tsx new file mode 100644 index 0000000..0c7a3d2 --- /dev/null +++ b/src/components/motion/input.tsx @@ -0,0 +1,278 @@ +"use client"; +// beui.dev/components/motion/input + +import { + AnimatePresence, + animate, + motion, + useReducedMotion, +} from "motion/react"; +import { + useEffect, + forwardRef, + useId, + useImperativeHandle, + useLayoutEffect, + useRef, + useState, + type InputHTMLAttributes, + type ReactNode, +} from "react"; +import { cn } from "src/lib/utils"; + +// Caret glide — snappy enough to feel attached to the keystroke, soft enough to read. +const CARET_SPRING = { + type: "spring", + stiffness: 700, + damping: 40, + mass: 0.5, +} as const; + +// Horizontal padding inside the field, in px. Wider when an icon occupies the edge. +const EDGE_PAD = 14; +const ICON_PAD = 40; + +export interface InputProps extends Omit< + InputHTMLAttributes, + "value" | "defaultValue" | "onChange" +> { + label?: string; + value?: string; + defaultValue?: string; + onChange?: (value: string) => void; + /** Truthy error triggers a shake, red border and (if a string) a message. */ + error?: string | boolean; + success?: boolean; + leftIcon?: ReactNode; + rightIcon?: ReactNode; + className?: string; +} + +export const Input = forwardRef(function Input( + { + label, + value: valueProp, + defaultValue, + onChange, + error, + success, + leftIcon, + rightIcon, + className, + disabled, + id: idProp, + type, + ...rest + }, + ref, +) { + // Password masks characters as dots, so a plain-text mirror can't measure the + // caret position. Use the native caret there; the gliding caret is for text. + const customCaret = type !== "password"; + const reactId = useId(); + const id = idProp ?? reactId; + const reduce = useReducedMotion(); + + const controlled = valueProp !== undefined; + const [internal, setInternal] = useState(defaultValue ?? ""); + const value = controlled ? (valueProp ?? "") : internal; + + const [focused, setFocused] = useState(false); + const [caretIndex, setCaretIndex] = useState(null); + const [caretX, setCaretX] = useState(0); + const [scrollLeft, setScrollLeft] = useState(0); + + const fieldRef = useRef(null); + const inputRef = useRef(null); + const mirrorRef = useRef(null); + useImperativeHandle(ref, () => inputRef.current as HTMLInputElement); + + const hasError = Boolean(error); + const errorMessage = typeof error === "string" ? error : null; + const index = caretIndex ?? value.length; + + // Right edge shows the success check, otherwise the caller's right icon. + const rightSlot = success ? null : rightIcon; + const leftPad = leftIcon ? ICON_PAD : EDGE_PAD; + const rightPad = rightSlot || success ? ICON_PAD : EDGE_PAD; + + // Measure text width up to the caret, and read the input's scroll so the + // caret stays aligned once the value overflows and the field scrolls. + useLayoutEffect(() => { + if (mirrorRef.current) { + mirrorRef.current.textContent = value.slice(0, index); + setCaretX(mirrorRef.current.offsetWidth); + } + if (inputRef.current) setScrollLeft(inputRef.current.scrollLeft); + }, [value, index]); + + // Shake the field when an error appears. + useEffect(() => { + if (!fieldRef.current || reduce || !hasError) return; + animate( + fieldRef.current, + { x: [0, -6, 6, -4, 4, -2, 0] }, + { duration: 0.45 }, + ); + }, [hasError, reduce]); + + const handleChange = (next: string) => { + if (!controlled) setInternal(next); + onChange?.(next); + }; + + return ( +
+ {label ? ( + + ) : null} + +
+ {leftIcon ? ( + + {leftIcon} + + ) : null} + + { + handleChange(e.target.value); + setCaretIndex(e.target.selectionStart); + }} + onFocus={(e) => { + setFocused(true); + setCaretIndex(e.target.selectionStart); + }} + onBlur={() => setFocused(false)} + onSelect={(e) => setCaretIndex(e.currentTarget.selectionStart)} + onScroll={(e) => setScrollLeft(e.currentTarget.scrollLeft)} + className={cn( + "peer h-full w-full bg-transparent text-sm leading-6 text-foreground outline-none", + customCaret ? "caret-transparent" : "caret-foreground", + "placeholder:text-muted-foreground/60", + "disabled:opacity-50", + disabled && "cursor-not-allowed", + )} + {...rest} + /> + + {customCaret ? ( + <> + {/* Hidden mirror measures caret x for the value up to the caret index. */} + + + {/* Custom caret glides between positions and blinks while focused. */} + + + ) : null} + + {success ? ( + + + + ) : rightSlot ? ( + + {rightSlot} + + ) : null} +
+ + + {errorMessage ? ( + + {errorMessage} + + ) : null} + +
+ ); +}); diff --git a/src/components/motion/magnetic.tsx b/src/components/motion/magnetic.tsx new file mode 100644 index 0000000..54b4a9a --- /dev/null +++ b/src/components/motion/magnetic.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { + motion, + useMotionValue, + useReducedMotion, + useSpring, +} from "motion/react"; +import { useRef, type ReactNode } from "react"; +import { SPRING_MOUSE } from "src/lib/ease"; +import { useHoverCapable } from "src/lib/hooks/use-hover-capable"; +import { cn } from "src/lib/utils"; + +export interface MagneticProps { + children: ReactNode; + strength?: number; + className?: string; +} + +export function Magnetic({ + children, + strength = 0.35, + className, +}: MagneticProps) { + const ref = useRef(null); + const reduce = useReducedMotion(); + const canHover = useHoverCapable(); + // Decorative cursor-follow: skip on touch (phantom hover) and reduced motion. + const enabled = !reduce && canHover; + const translateX = useMotionValue(0); + const translateY = useMotionValue(0); + const springX = useSpring(translateX, SPRING_MOUSE); + const springY = useSpring(translateY, SPRING_MOUSE); + + const onMove = (event: React.MouseEvent) => { + const element = ref.current; + if (!element || !enabled) return; + const rect = element.getBoundingClientRect(); + translateX.set((event.clientX - rect.left - rect.width / 2) * strength); + translateY.set((event.clientY - rect.top - rect.height / 2) * strength); + }; + + const onLeave = () => { + translateX.set(0); + translateY.set(0); + }; + + return ( + + {children} + + ); +} diff --git a/src/components/motion/marquee.tsx b/src/components/motion/marquee.tsx new file mode 100644 index 0000000..cf0dbd8 --- /dev/null +++ b/src/components/motion/marquee.tsx @@ -0,0 +1,72 @@ +import { Children, type ReactNode } from "react"; +import { cn } from "src/lib/utils"; + +export interface MarqueeProps { + children: ReactNode; + direction?: "left" | "right" | "up" | "down"; + speed?: number; + pauseOnHover?: boolean; + gap?: string; + className?: string; + fade?: boolean; +} + +export function Marquee({ + children, + direction = "left", + speed = 30, + pauseOnHover = true, + gap = "1rem", + className, + fade = true, +}: MarqueeProps) { + const vertical = direction === "up" || direction === "down"; + const reverse = direction === "right" || direction === "down"; + const items = Children.toArray(children); + const getItemKey = (child: ReactNode) => + typeof child === "object" && child !== null && "key" in child + ? String(child.key) + : String(child); + + return ( +
+ {[0, 1].map((dup) => ( +
+ {items.map((child) => ( +
+ {child} +
+ ))} +
+ ))} +
+ ); +} diff --git a/src/components/motion/number-ticker.tsx b/src/components/motion/number-ticker.tsx new file mode 100644 index 0000000..1f24580 --- /dev/null +++ b/src/components/motion/number-ticker.tsx @@ -0,0 +1,187 @@ +"use client"; +// beui.dev/components/motion/number + +import { animate, motion, useInView, useReducedMotion } from "motion/react"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +export interface NumberTickerProps { + value: number; + /** Digits to pad to (left). */ + pad?: number; + /** Per-digit roll duration in seconds. */ + duration?: number; + /** Stagger between digits. */ + stagger?: number; + /** Render only after the element enters the viewport. */ + startOnView?: boolean; + prefix?: string; + suffix?: string; + /** Add a small blur during digit rolls. */ + blur?: boolean; + className?: string; + digitClassName?: string; + /** Insert locale group separators (commas). Server-component safe. */ + locale?: boolean; + /** Custom formatter. Client-only — server components must use `locale` instead. */ + format?: (value: number) => string; +} + +const DIGIT_HEIGHT_EM = 1.1; +const DIGITS = Array.from({ length: 10 }, (_, n) => n); + +export function NumberTicker({ + value, + pad, + duration = 0.9, + stagger = 0.04, + startOnView = true, + prefix, + suffix, + blur = false, + className, + digitClassName, + locale, + format, +}: NumberTickerProps) { + const containerRef = useRef(null); + const inView = useInView(containerRef, { once: true, amount: 0.6 }); + const [armed, setArmed] = useState(!startOnView); + + useEffect(() => { + if (startOnView && inView) setArmed(true); + }, [startOnView, inView]); + + const text = useMemo(() => { + const rounded = Math.round(value); + const formatted = format + ? format(rounded) + : locale + ? rounded.toLocaleString() + : rounded.toString(); + return pad ? formatted.padStart(pad, "0") : formatted; + }, [value, pad, format, locale]); + const glyphs = useMemo(() => { + const chars = text.split(""); + // Key by place value (position from the right): a changing digit keeps its + // identity and rolls to the new value instead of remounting and replaying + // from 0. Growing numbers add glyphs on the left without re-keying the + // ones, tens, hundreds already on screen. + return chars.map((char, index) => ({ + char, + id: `g-${chars.length - 1 - index}`, + })); + }, [text]); + const readableText = `${prefix ?? ""}${text}${suffix ?? ""}`; + + // Stagger is an entrance flourish. Once the reveal has played, value + // changes roll every digit immediately — a per-digit delay on live updates + // reads as lag. + const [entered, setEntered] = useState(false); + useEffect(() => { + if (!armed || entered) return; + const total = (duration + glyphs.length * stagger) * 1000; + const timeoutId = window.setTimeout(() => setEntered(true), total); + return () => window.clearTimeout(timeoutId); + }, [armed, entered, duration, stagger, glyphs.length]); + + return ( + + {readableText} + + + ); +} + +function Digit({ + digit, + delay, + duration, + blur, + className, +}: { + digit: number; + delay: number; + duration: number; + blur: boolean; + className?: string; +}) { + const reduce = useReducedMotion(); + const columnRef = useRef(null); + + useEffect(() => { + if (reduce || !blur || !columnRef.current || !Number.isFinite(digit)) { + return; + } + + const node = columnRef.current; + const controls = animate( + node, + { filter: ["blur(10px)", "blur(0px)"] }, + { + duration: Math.min(duration * 0.75, 0.32), + delay, + ease: EASE_OUT, + }, + ); + + return () => { + controls.stop(); + node.style.filter = "blur(0px)"; + }; + }, [blur, delay, digit, duration, reduce]); + + return ( + + + {DIGITS.map((n) => ( + + {n} + + ))} + + + ); +} diff --git a/src/components/motion/radio.tsx b/src/components/motion/radio.tsx new file mode 100644 index 0000000..54bb885 --- /dev/null +++ b/src/components/motion/radio.tsx @@ -0,0 +1,145 @@ +"use client"; +// beui.dev/components/motion/radio + +import { motion, MotionConfig, useReducedMotion } from "motion/react"; +import { + createContext, + useContext, + useId, + useState, + type ReactNode, +} from "react"; +import { SPRING_LAYOUT, SPRING_PRESS } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +type RadioCtx = { + value: string; + setValue: (value: string) => void; + layoutId: string; +}; + +const RadioCtx = createContext(null); + +function useRadioGroup() { + const ctx = useContext(RadioCtx); + if (!ctx) { + throw new Error("RadioGroupItem must be used inside "); + } + return ctx; +} + +export interface RadioGroupProps { + value?: string; + defaultValue?: string; + onValueChange?: (value: string) => void; + children: ReactNode; + className?: string; + orientation?: "vertical" | "horizontal"; +} + +export function RadioGroup({ + value, + defaultValue = "", + onValueChange, + children, + className, + orientation = "vertical", +}: RadioGroupProps) { + const [internal, setInternal] = useState(defaultValue); + const layoutId = useId(); + const reduce = useReducedMotion(); + const controlled = value !== undefined; + const current = controlled ? value : internal; + const setValue = (next: string) => { + if (!controlled) setInternal(next); + onValueChange?.(next); + }; + + return ( + + +
+ {children} +
+
+
+ ); +} + +export interface RadioGroupItemProps { + value: string; + label?: string; + disabled?: boolean; + className?: string; + id?: string; +} + +export function RadioGroupItem({ + value, + label, + disabled, + className, + id: idProp, +}: RadioGroupItemProps) { + const { value: groupValue, setValue, layoutId } = useRadioGroup(); + const autoId = useId(); + const id = idProp ?? autoId; + const reduce = useReducedMotion(); + const selected = groupValue === value; + + return ( + + ); +} diff --git a/src/components/motion/select.tsx b/src/components/motion/select.tsx new file mode 100644 index 0000000..0f94d78 --- /dev/null +++ b/src/components/motion/select.tsx @@ -0,0 +1,420 @@ +"use client"; +// beui.dev/components/motion/select + +import { Check, ChevronDown } from "lucide-react"; +import { + motion, + type Transition, + useReducedMotion, + type Variants, +} from "motion/react"; +import { + createContext, + type ReactNode, + useCallback, + useContext, + useEffect, + useId, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +// Spring with bounce powers the unfold/separation; per-property timings in the +// content choreograph it (see SelectContent). Mirrors bouncy-accordion's feel. +const CHEVRON_TRANSITION: Transition = { + type: "spring", + duration: 0.4, + bounce: 0.3, +}; + +const LIST_VARIANTS: Variants = { + hidden: {}, + show: { transition: { staggerChildren: 0.035, delayChildren: 0.05 } }, +}; +const ITEM_VARIANTS: Variants = { + hidden: { opacity: 0, y: -6, filter: "blur(3px)" }, + show: { opacity: 1, y: 0, filter: "blur(0px)" }, +}; + +type Placement = "bottom" | "top"; + +interface SelectContextValue { + value: string | undefined; + open: boolean; + setOpen: (open: boolean) => void; + select: (value: string) => void; + register: (value: string, label: string) => void; + unregister: (value: string) => void; + labelFor: (value: string | undefined) => string | undefined; + reduce: boolean; + triggerId: string; + listId: string; + disabled: boolean; + placement: Placement; + setPlacement: (p: Placement) => void; +} + +const SelectContext = createContext(null); + +function useSelectContext(component: string) { + const ctx = useContext(SelectContext); + if (!ctx) throw new Error(`${component} must be used within onChange(e.target.value)} + placeholder="Empty" + className="-mx-2 w-full min-w-0 appearance-none rounded-md border-0 bg-transparent px-2 py-1 text-foreground outline-none transition-colors placeholder:text-muted-foreground/40 focus:bg-muted focus:ring-1 focus:ring-ring" + /> + ); +} diff --git a/src/components/motion/table/index.tsx b/src/components/motion/table/index.tsx new file mode 100644 index 0000000..8b804bc --- /dev/null +++ b/src/components/motion/table/index.tsx @@ -0,0 +1,363 @@ +"use client"; +// beui.dev/components/motion/table + +import { useVirtualizer } from "@tanstack/react-virtual"; +import { useReducedMotion } from "motion/react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Checkbox } from "src/components/motion/checkbox"; +import { cn } from "src/lib/utils"; +import { EditableCell } from "./editable-cell"; +import { RowHandle } from "./row-handle"; +import { SkeletonRows } from "./skeleton-rows"; +import { TableHeader } from "./table-header"; +import type { HeaderCellRefs, TableProps } from "./types"; +import { useColumnReorder } from "./use-column-reorder"; +import { useColumnResize } from "./use-column-resize"; +import { useColumnSort } from "./use-column-sort"; +import { useRowSelection } from "./use-row-selection"; +import { CHECKBOX_WIDTH, alignText, readCell } from "./utils"; + +export type { + SortDirection, + SortState, + TableColumn, + TableProps, +} from "./types"; + +/** Data table with optional sorting, selection, resizing, and virtual rows. */ +export function Table({ + data, + columns, + getRowId, + selectable = false, + selectedRowIds, + defaultSelectedRowIds, + onSelectionChange, + sort: sortProp, + defaultSort = null, + onSortChange, + resizable = false, + minColumnWidth = 64, + onColumnResize, + reorderable = false, + onColumnOrderChange, + onCellEdit, + onColumnRename, + onInsertRow, + onDeleteRow, + onInsertColumn, + onDeleteColumn, + rowHeight = 48, + height = 440, + overscan = 10, + onEndReached, + loading = false, + skeletonRows = 3, + emptyState = "No data", + className, +}: TableProps) { + const reduce = useReducedMotion(); + const scrollRef = useRef(null); + const autoHeight = height === "auto"; + const viewportHeight = + height === "auto" + ? Math.max(rowHeight, data.length * rowHeight + rowHeight) + : height; + const thRefs: HeaderCellRefs = useRef< + Record + >({}); + + const rows = useMemo( + () => + data.map((row, index) => ({ + row, + id: getRowId ? getRowId(row, index) : String(index), + })), + [data, getRowId], + ); + + const { + orderedColumns, + dragKey, + dropIndex, + startReorder, + moveReorder, + endReorder, + } = useColumnReorder({ columns, thRefs, onColumnOrderChange }); + + const { sort, sortedRows, toggleSort } = useColumnSort({ + rows, + columns, + sort: sortProp, + defaultSort, + onSortChange, + }); + + const { widths, startResize, moveResize, endResize } = useColumnResize({ + orderedColumns, + thRefs, + minColumnWidth, + onColumnResize, + }); + + const { selected, allSelected, someSelected, toggleAll, toggleRow } = + useRowSelection({ + sortedRows, + selectedRowIds, + defaultSelectedRowIds, + onSelectionChange, + }); + + const virtualizer = useVirtualizer({ + count: sortedRows.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => rowHeight, + overscan, + }); + + const virtualItems = autoHeight ? [] : virtualizer.getVirtualItems(); + const visibleIndexes = autoHeight + ? sortedRows.map((_, index) => index) + : virtualItems.map((item) => item.index); + const totalSize = virtualizer.getTotalSize(); + const paddingTop = + !autoHeight && virtualItems.length > 0 ? virtualItems[0].start : 0; + const paddingBottom = + !autoHeight && virtualItems.length > 0 + ? totalSize - virtualItems[virtualItems.length - 1].end + : 0; + + const hasRowMenu = Boolean(onInsertRow || onDeleteRow); + const hasColumnMenu = Boolean(onInsertColumn || onDeleteColumn); + // Only shrink-wrap (w-max) once every column has an explicit resized width; + // otherwise stay fill-width so a flexible column can't size to cell content. + const sized = + orderedColumns.length > 0 && + orderedColumns.every((c) => widths[c.key] != null); + + // Infinite scroll: fire onEndReached once per near-bottom dwell, paused while + // loading; the guard resets when the load completes. + const endReachedRef = useRef(false); + useEffect(() => { + if (!loading) endReachedRef.current = false; + }, [loading]); + const handleScroll = useCallback(() => { + const el = scrollRef.current; + if (!el || !onEndReached || loading || endReachedRef.current) return; + if (el.scrollHeight - el.scrollTop - el.clientHeight < rowHeight * 4) { + endReachedRef.current = true; + onEndReached(); + } + }, [onEndReached, loading, rowHeight]); + const [activeColumn, setActiveColumn] = useState(null); + // Small delay on leave so the pointer can cross the gap from the header cell + // to the portal handle without the column deactivating. + const deactivateTimer = useRef | null>(null); + const activateColumn = useCallback((key: string) => { + if (deactivateTimer.current) clearTimeout(deactivateTimer.current); + deactivateTimer.current = null; + setActiveColumn(key); + }, []); + const deactivateColumn = useCallback(() => { + if (deactivateTimer.current) clearTimeout(deactivateTimer.current); + deactivateTimer.current = setTimeout(() => setActiveColumn(null), 100); + }, []); + + const rowRefs = useRef>({}); + const [activeRow, setActiveRow] = useState<{ + id: string; + index: number; + } | null>(null); + const rowTimer = useRef | null>(null); + const activateRow = useCallback((id: string, index: number) => { + if (rowTimer.current) clearTimeout(rowTimer.current); + rowTimer.current = null; + setActiveRow({ id, index }); + }, []); + const deactivateRow = useCallback(() => { + if (rowTimer.current) clearTimeout(rowTimer.current); + rowTimer.current = setTimeout(() => setActiveRow(null), 100); + }, []); + const activeRowEl = activeRow ? rowRefs.current[activeRow.id] : null; + // Real columns + checkbox; the trailing spacer adds one more in colSpans. + const leadColumns = columns.length + (selectable ? 1 : 0); + + return ( +
+
+
+ + {selectable ? : null} + {orderedColumns.map((column) => { + const override = widths[column.key]; + const width = override ? `${override}px` : column.width; + return ( + + ); + })} + {/* Empty filler owns the leftover space — no gap, content unpinned. */} + + + + + + + {sortedRows.length === 0 ? ( + loading ? ( + + ) : ( + + + + ) + ) : ( + <> + {paddingTop > 0 ? ( + + + ) : null} + {visibleIndexes.map((rowIndex) => { + const entry = sortedRows[rowIndex]; + const isSelected = selected.has(entry.id); + return ( + { + rowRefs.current[entry.id] = el; + }} + data-selected={isSelected} + style={{ height: rowHeight }} + onPointerEnter={ + hasRowMenu + ? () => activateRow(entry.id, rowIndex) + : undefined + } + onPointerLeave={hasRowMenu ? deactivateRow : undefined} + className={cn( + "border-border/60 border-b transition-colors", + "data-[selected=true]:bg-primary/5", + "hover:bg-muted/50", + )} + > + {selectable ? ( + + ) : null} + {orderedColumns.map((column) => ( + + ))} + + ); + })} + {paddingBottom > 0 ? ( + + + ) : null} + {loading ? ( + + ) : null} + + )} + +
+ {emptyState} +
+
+
+ toggleRow(entry.id)} + aria-label={`Select row ${rowIndex + 1}`} + /> +
+
+ {!column.cell && column.editable ? ( + + onCellEdit?.(entry.id, column.key, next) + } + /> + ) : ( + readCell(entry.row, column) + )} + +
+
+
+ {hasRowMenu && activeRow ? ( + activateRow(activeRow.id, activeRow.index)} + onLeave={deactivateRow} + /> + ) : null} +
+ ); +} diff --git a/src/components/motion/table/row-handle.tsx b/src/components/motion/table/row-handle.tsx new file mode 100644 index 0000000..4955c56 --- /dev/null +++ b/src/components/motion/table/row-handle.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { + ArrowDownToLine, + ArrowUpToLine, + MoreVertical, + Trash2, +} from "lucide-react"; +import { useEffect } from "react"; +import { createPortal } from "react-dom"; +import { TableMenu } from "./table-menu"; + +/** The row handle, portaled so it can sit on the row's left border without the + * scroll container clipping it. Straddles the border to bridge hover. */ +export function RowHandle({ + rowEl, + id, + index, + onInsertRow, + onDeleteRow, + onEnter, + onLeave, +}: { + rowEl: HTMLTableRowElement | null; + id: string; + index: number; + onInsertRow?: (index: number, position: "before" | "after") => void; + onDeleteRow?: (rowId: string, index: number) => void; + onEnter: () => void; + onLeave: () => void; +}) { + useEffect(() => { + window.addEventListener("scroll", onLeave, true); + return () => window.removeEventListener("scroll", onLeave, true); + }, [onLeave]); + + if (!rowEl || typeof document === "undefined") return null; + const rect = rowEl.getBoundingClientRect(); + + return createPortal( +
+ } + items={[ + ...(onInsertRow + ? [ + { + label: "Insert before", + icon: , + onSelect: () => onInsertRow(index, "before"), + }, + { + label: "Insert after", + icon: , + onSelect: () => onInsertRow(index, "after"), + }, + ] + : []), + ...(onDeleteRow + ? [ + { + label: "Delete row", + icon: , + destructive: true, + onSelect: () => onDeleteRow(id, index), + }, + ] + : []), + ]} + /> +
, + document.body, + ); +} diff --git a/src/components/motion/table/skeleton-rows.tsx b/src/components/motion/table/skeleton-rows.tsx new file mode 100644 index 0000000..8384677 --- /dev/null +++ b/src/components/motion/table/skeleton-rows.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { cn } from "src/lib/utils"; +import type { TableColumn } from "./types"; +import { alignText } from "./utils"; + +export function SkeletonRows({ + count, + columns, + selectable, + rowHeight, +}: { + count: number; + columns: TableColumn[]; + selectable: boolean; + rowHeight: number; +}) { + return ( + <> + {Array.from({ length: count }, (_, r) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: static placeholder rows + + {selectable ? : null} + {columns.map((column) => ( + +
+ + ))} + + + ))} + + ); +} diff --git a/src/components/motion/table/table-header.tsx b/src/components/motion/table/table-header.tsx new file mode 100644 index 0000000..97d4c39 --- /dev/null +++ b/src/components/motion/table/table-header.tsx @@ -0,0 +1,338 @@ +"use client"; + +import { + ArrowLeftToLine, + ArrowRightToLine, + ChevronUp, + GripVertical, + MoreHorizontal, + Trash2, +} from "lucide-react"; +import { motion } from "motion/react"; +import { type PointerEvent as ReactPointerEvent, useEffect } from "react"; +import { createPortal } from "react-dom"; +import { Checkbox } from "src/components/motion/checkbox"; +import { EASE_OUT, SPRING_PRESS } from "src/lib/ease"; +import { cn } from "src/lib/utils"; +import { TableMenu } from "./table-menu"; +import type { + HeaderCellRefs, + InsertPosition, + SortState, + TableColumn, +} from "./types"; +import { alignFlex, alignText, COLUMN_ACTIVE_SHADOW } from "./utils"; + +export interface TableHeaderProps { + columns: TableColumn[]; + rowHeight: number; + reduce: boolean; + thRefs: HeaderCellRefs; + selectable: boolean; + allSelected: boolean; + someSelected: boolean; + onToggleAll: () => void; + sort: SortState | null; + onToggleSort: (key: string) => void; + resizable: boolean; + onResizeStart: (key: string, e: ReactPointerEvent) => void; + onResizeMove: (e: ReactPointerEvent) => void; + onResizeEnd: (e: ReactPointerEvent) => void; + reorderable: boolean; + dragKey: string | null; + dropIndex: number | null; + onReorderStart: (key: string, e: ReactPointerEvent) => void; + onReorderMove: (e: ReactPointerEvent) => void; + onReorderEnd: (e: ReactPointerEvent) => void; + onInsertColumn?: (index: number, position: InsertPosition) => void; + onDeleteColumn?: (columnKey: string, index: number) => void; + onColumnRename?: (columnKey: string, value: string) => void; + activeColumn: string | null; + onColumnActivate?: (key: string) => void; + onColumnDeactivate?: () => void; +} + +/** Column insert / delete menu items shared by the header cell and the portal handle. */ +function columnMenuItems( + column: TableColumn, + index: number, + onInsertColumn?: (index: number, position: InsertPosition) => void, + onDeleteColumn?: (columnKey: string, index: number) => void, +) { + return [ + ...(onInsertColumn + ? [ + { + label: "Insert before", + icon: , + onSelect: () => onInsertColumn(index, "before"), + }, + { + label: "Insert after", + icon: , + onSelect: () => onInsertColumn(index, "after"), + }, + ] + : []), + ...(onDeleteColumn + ? [ + { + label: "Delete column", + icon: , + destructive: true, + onSelect: () => onDeleteColumn(column.key, index), + }, + ] + : []), + ]; +} + +/** The ellipse handle, portaled so it can sit on the column's top border without + * the scroll container clipping it. Straddles the border to bridge hover. */ +function ColumnHandle({ + column, + index, + thRefs, + onInsertColumn, + onDeleteColumn, + onEnter, + onLeave, +}: { + column: TableColumn; + index: number; + thRefs: HeaderCellRefs; + onInsertColumn?: (index: number, position: InsertPosition) => void; + onDeleteColumn?: (columnKey: string, index: number) => void; + onEnter: () => void; + onLeave: () => void; +}) { + useEffect(() => { + window.addEventListener("scroll", onLeave, true); + return () => window.removeEventListener("scroll", onLeave, true); + }, [onLeave]); + + const el = thRefs.current[column.key]; + if (!el || typeof document === "undefined") return null; + const rect = el.getBoundingClientRect(); + + return createPortal( +
+ } + items={columnMenuItems(column, index, onInsertColumn, onDeleteColumn)} + /> +
, + document.body, + ); +} + +/** Renders table headers, sorting controls, and optional column handles. */ +export function TableHeader({ + columns, + rowHeight, + reduce, + thRefs, + selectable, + allSelected, + someSelected, + onToggleAll, + sort, + onToggleSort, + resizable, + onResizeStart, + onResizeMove, + onResizeEnd, + reorderable, + dragKey, + dropIndex, + onReorderStart, + onReorderMove, + onReorderEnd, + onInsertColumn, + onDeleteColumn, + onColumnRename, + activeColumn, + onColumnActivate, + onColumnDeactivate, +}: TableHeaderProps) { + const hasColumnMenu = Boolean(onInsertColumn || onDeleteColumn); + const activeIndex = columns.findIndex((c) => c.key === activeColumn); + return ( + <> + {hasColumnMenu && activeColumn && activeIndex >= 0 ? ( + onColumnActivate?.(activeColumn)} + onLeave={() => onColumnDeactivate?.()} + /> + ) : null} + + + {selectable ? ( + +
+ +
+ + ) : null} + {columns.map((column, index) => { + const active = sort?.key === column.key; + const isDragging = dragKey === column.key; + const isActive = activeColumn === column.key; + return ( + { + thRefs.current[column.key] = el; + }} + onPointerEnter={() => onColumnActivate?.(column.key)} + onPointerLeave={() => onColumnDeactivate?.()} + style={ + isActive ? { boxShadow: COLUMN_ACTIVE_SHADOW } : undefined + } + aria-sort={ + active + ? sort?.direction === "asc" + ? "ascending" + : "descending" + : undefined + } + data-drop={dragKey ? dropIndex === index : undefined} + data-dropend={ + dragKey + ? dropIndex === columns.length && + index === columns.length - 1 + : undefined + } + className={cn( + "group sticky top-0 z-10 border-border border-b bg-muted p-0 font-medium text-muted-foreground", + "data-[drop=true]:before:absolute data-[drop=true]:before:inset-y-0 data-[drop=true]:before:left-0 data-[drop=true]:before:w-0.5 data-[drop=true]:before:bg-primary", + "data-[dropend=true]:after:absolute data-[dropend=true]:after:inset-y-0 data-[dropend=true]:after:right-0 data-[dropend=true]:after:w-0.5 data-[dropend=true]:after:bg-primary", + )} + > + + {reorderable ? ( + + ) : null} + {column.sortable ? ( + + ) : onColumnRename ? ( + + onColumnRename(column.key, e.target.value) + } + className={cn( + "min-w-0 flex-1 truncate appearance-none rounded-md border-0 bg-transparent px-4 font-medium text-muted-foreground outline-none transition-colors focus:bg-muted focus:text-foreground", + alignText(column.align), + )} + /> + ) : ( + + {column.header} + + )} + + {resizable ? ( + + {open && typeof document !== "undefined" + ? createPortal( + <> +
setCoords(null)} + /> + + {items.map((item) => ( + + ))} + + , + document.body, + ) + : null} + + ); +} diff --git a/src/components/motion/table/types.ts b/src/components/motion/table/types.ts new file mode 100644 index 0000000..30772a7 --- /dev/null +++ b/src/components/motion/table/types.ts @@ -0,0 +1,86 @@ +import type { ReactNode } from "react"; + +export type SortDirection = "asc" | "desc"; + +export type SortState = { + key: string; + direction: SortDirection; +}; + +export type TableColumn = { + /** Stable key; also the default object property read for the cell + sort value. */ + key: string; + /** Header content. */ + header: ReactNode; + /** Allow clicking the header to sort by this column. */ + sortable?: boolean; + /** Cell text alignment. */ + align?: "left" | "center" | "right"; + /** Column width as a CSS length, e.g. "160px" or "20%". Omit to share remaining space equally. */ + width?: string; + /** Custom cell renderer. Falls back to `row[key]`. */ + cell?: (row: T) => ReactNode; + /** Render an inline text input for this column's cells (ignored when `cell` is set). */ + editable?: boolean; + /** Value used for sorting. Falls back to `row[key]`. */ + sortValue?: (row: T) => string | number; +}; + +export type InsertPosition = "before" | "after"; + +export interface TableProps { + data: T[]; + columns: TableColumn[]; + /** Stable id per row, required for correct selection across sorts. Defaults to row index. */ + getRowId?: (row: T, index: number) => string; + /** Render a leading checkbox column with select-all in the header. */ + selectable?: boolean; + selectedRowIds?: string[]; + defaultSelectedRowIds?: string[]; + onSelectionChange?: (ids: string[]) => void; + sort?: SortState | null; + defaultSort?: SortState | null; + onSortChange?: (sort: SortState | null) => void; + /** Allow dragging the right edge of a header to resize that column. */ + resizable?: boolean; + /** Minimum column width in px when resizing. */ + minColumnWidth?: number; + onColumnResize?: (key: string, width: number) => void; + /** Allow dragging a header grip to reorder columns. */ + reorderable?: boolean; + onColumnOrderChange?: (keys: string[]) => void; + /** Called when an `editable` cell changes. */ + onCellEdit?: (rowId: string, columnKey: string, value: string) => void; + /** When set, non-sortable headers become editable inputs for the column name. */ + onColumnRename?: (columnKey: string, value: string) => void; + /** Enables the row menu (Insert before / after). Receives the target index. */ + onInsertRow?: (index: number, position: InsertPosition) => void; + /** Enables Delete in the row menu. */ + onDeleteRow?: (rowId: string, index: number) => void; + /** Enables the column menu (Insert before / after). Receives the target column index. */ + onInsertColumn?: (index: number, position: InsertPosition) => void; + /** Enables Delete in the column menu. */ + onDeleteColumn?: (columnKey: string, index: number) => void; + /** Fixed row height in px — required for virtualization. */ + rowHeight?: number; + /** Scroll viewport height in px, or "auto" to render without an internal scroller. */ + height?: number | "auto"; + /** Rows rendered above/below the viewport. */ + overscan?: number; + /** Fires when the viewport scrolls near the bottom — load the next page. */ + onEndReached?: () => void; + /** Currently fetching — shows skeleton rows and pauses `onEndReached`. */ + loading?: boolean; + /** How many skeleton rows to show while loading more (default 3). */ + skeletonRows?: number; + emptyState?: ReactNode; + className?: string; +} + +/** A data row paired with its stable id. */ +export type TableRow = { row: T; id: string }; + +/** Ref map from column key to its header cell, shared across the resize/reorder hooks. */ +export type HeaderCellRefs = { + current: Record; +}; diff --git a/src/components/motion/table/use-column-reorder.ts b/src/components/motion/table/use-column-reorder.ts new file mode 100644 index 0000000..325462e --- /dev/null +++ b/src/components/motion/table/use-column-reorder.ts @@ -0,0 +1,104 @@ +import { + type PointerEvent as ReactPointerEvent, + useCallback, + useMemo, + useState, +} from "react"; +import type { HeaderCellRefs, TableColumn } from "./types"; + +export function useColumnReorder({ + columns, + thRefs, + onColumnOrderChange, +}: { + columns: TableColumn[]; + thRefs: HeaderCellRefs; + onColumnOrderChange?: (keys: string[]) => void; +}) { + const [order, setOrder] = useState(() => columns.map((c) => c.key)); + const [dragKey, setDragKey] = useState(null); + const [dropIndex, setDropIndex] = useState(null); + + // Apply the current order, tolerating columns added/removed at runtime. New + // columns are placed at their position in `columns` (after their left + // neighbor), not appended — so an inserted column lands where it was added. + const orderedColumns = useMemo(() => { + const byKey = new Map(columns.map((c) => [c.key, c])); + const resultKeys = order.filter((k) => byKey.has(k)); + const present = new Set(resultKeys); + columns.forEach((column, i) => { + if (present.has(column.key)) return; + let at = resultKeys.length; + if (i === 0) { + at = 0; + } else { + const idx = resultKeys.indexOf(columns[i - 1].key); + at = idx === -1 ? i : idx + 1; + } + resultKeys.splice(at, 0, column.key); + present.add(column.key); + }); + return resultKeys + .map((k) => byKey.get(k)) + .filter((c): c is TableColumn => c !== undefined); + }, [order, columns]); + + const dropIndexFor = useCallback( + (clientX: number) => { + for (let i = 0; i < orderedColumns.length; i++) { + const rect = + thRefs.current[orderedColumns[i].key]?.getBoundingClientRect(); + if (rect && clientX < rect.left + rect.width / 2) return i; + } + return orderedColumns.length; + }, + [orderedColumns, thRefs], + ); + + const startReorder = useCallback((key: string, e: ReactPointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragKey(key); + e.currentTarget.setPointerCapture(e.pointerId); + }, []); + + const moveReorder = useCallback( + (e: ReactPointerEvent) => { + if (!dragKey) return; + setDropIndex(dropIndexFor(e.clientX)); + }, + [dragKey, dropIndexFor], + ); + + const endReorder = useCallback( + (e: ReactPointerEvent) => { + if (e.currentTarget.hasPointerCapture(e.pointerId)) { + e.currentTarget.releasePointerCapture(e.pointerId); + } + if (dragKey && dropIndex !== null) { + const keys = orderedColumns.map((c) => c.key); + const from = keys.indexOf(dragKey); + if (from !== -1) { + const without = keys.filter((_, i) => i !== from); + let to = dropIndex; + if (from < to) to--; + without.splice(to, 0, dragKey); + setOrder(without); + onColumnOrderChange?.(without); + } + } + setDragKey(null); + setDropIndex(null); + }, + [dragKey, dropIndex, orderedColumns, onColumnOrderChange], + ); + + return { + orderedColumns, + dragKey, + dropIndex, + startReorder, + moveReorder, + endReorder, + }; +} diff --git a/src/components/motion/table/use-column-resize.ts b/src/components/motion/table/use-column-resize.ts new file mode 100644 index 0000000..43b5ee9 --- /dev/null +++ b/src/components/motion/table/use-column-resize.ts @@ -0,0 +1,84 @@ +import { + type PointerEvent as ReactPointerEvent, + useCallback, + useRef, + useState, +} from "react"; +import type { HeaderCellRefs, TableColumn } from "./types"; + +export function useColumnResize({ + orderedColumns, + thRefs, + minColumnWidth, + onColumnResize, +}: { + orderedColumns: TableColumn[]; + thRefs: HeaderCellRefs; + minColumnWidth: number; + onColumnResize?: (key: string, width: number) => void; +}) { + const resizeRef = useRef<{ + key: string; + startX: number; + startWidth: number; + } | null>(null); + const [widths, setWidths] = useState>({}); + + const startResize = useCallback( + (key: string, e: ReactPointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + // Freeze every column to its current pixel width so resizing one only + // moves the trailing spacer, never the other columns. + setWidths((prev) => { + const snapshot = { ...prev }; + for (const column of orderedColumns) { + if (snapshot[column.key] == null) { + const measured = + thRefs.current[column.key]?.getBoundingClientRect().width; + snapshot[column.key] = measured + ? Math.round(measured) + : minColumnWidth; + } + } + resizeRef.current = { + key, + startX: e.clientX, + startWidth: snapshot[key], + }; + return snapshot; + }); + e.currentTarget.setPointerCapture(e.pointerId); + }, + [minColumnWidth, orderedColumns, thRefs], + ); + + const moveResize = useCallback( + (e: ReactPointerEvent) => { + const state = resizeRef.current; + if (!state) return; + const width = Math.max( + minColumnWidth, + state.startWidth + (e.clientX - state.startX), + ); + setWidths((prev) => ({ ...prev, [state.key]: width })); + }, + [minColumnWidth], + ); + + const endResize = useCallback( + (e: ReactPointerEvent) => { + const state = resizeRef.current; + resizeRef.current = null; + if (e.currentTarget.hasPointerCapture(e.pointerId)) { + e.currentTarget.releasePointerCapture(e.pointerId); + } + if (state) { + onColumnResize?.(state.key, widths[state.key] ?? state.startWidth); + } + }, + [onColumnResize, widths], + ); + + return { widths, startResize, moveResize, endResize }; +} diff --git a/src/components/motion/table/use-column-sort.ts b/src/components/motion/table/use-column-sort.ts new file mode 100644 index 0000000..ac182cd --- /dev/null +++ b/src/components/motion/table/use-column-sort.ts @@ -0,0 +1,64 @@ +import { useCallback, useMemo, useState } from "react"; +import type { SortState, TableColumn, TableRow } from "./types"; +import { readSortValue } from "./utils"; + +export function useColumnSort({ + rows, + columns, + sort: sortProp, + defaultSort = null, + onSortChange, +}: { + rows: TableRow[]; + columns: TableColumn[]; + sort?: SortState | null; + defaultSort?: SortState | null; + onSortChange?: (sort: SortState | null) => void; +}) { + const [internalSort, setInternalSort] = useState( + defaultSort, + ); + const sort = sortProp !== undefined ? sortProp : internalSort; + + const commit = useCallback( + (next: SortState | null) => { + if (sortProp === undefined) setInternalSort(next); + onSortChange?.(next); + }, + [sortProp, onSortChange], + ); + + const toggleSort = useCallback( + (key: string) => { + if (!sort || sort.key !== key) { + commit({ key, direction: "asc" }); + } else if (sort.direction === "asc") { + commit({ key, direction: "desc" }); + } else { + commit(null); + } + }, + [sort, commit], + ); + + const sortedRows = useMemo(() => { + if (!sort) return rows; + const column = columns.find((c) => c.key === sort.key); + if (!column) return rows; + const copy = [...rows]; + copy.sort((a, b) => { + const av = readSortValue(a.row, column); + const bv = readSortValue(b.row, column); + let cmp: number; + if (typeof av === "number" && typeof bv === "number") { + cmp = av - bv; + } else { + cmp = String(av).localeCompare(String(bv)); + } + return sort.direction === "asc" ? cmp : -cmp; + }); + return copy; + }, [rows, sort, columns]); + + return { sort, sortedRows, toggleSort }; +} diff --git a/src/components/motion/table/use-row-selection.ts b/src/components/motion/table/use-row-selection.ts new file mode 100644 index 0000000..de02e7a --- /dev/null +++ b/src/components/motion/table/use-row-selection.ts @@ -0,0 +1,57 @@ +import { useCallback, useMemo, useState } from "react"; +import type { TableRow } from "./types"; + +export function useRowSelection({ + sortedRows, + selectedRowIds, + defaultSelectedRowIds, + onSelectionChange, +}: { + sortedRows: TableRow[]; + selectedRowIds?: string[]; + defaultSelectedRowIds?: string[]; + onSelectionChange?: (ids: string[]) => void; +}) { + const [internalSelected, setInternalSelected] = useState>( + () => new Set(defaultSelectedRowIds), + ); + const selected = useMemo( + () => + selectedRowIds !== undefined ? new Set(selectedRowIds) : internalSelected, + [selectedRowIds, internalSelected], + ); + + const commit = useCallback( + (next: Set) => { + if (selectedRowIds === undefined) setInternalSelected(next); + onSelectionChange?.([...next]); + }, + [selectedRowIds, onSelectionChange], + ); + + const allSelected = + sortedRows.length > 0 && sortedRows.every((row) => selected.has(row.id)); + const someSelected = sortedRows.some((row) => selected.has(row.id)); + + const toggleAll = useCallback(() => { + const next = new Set(selected); + if (allSelected) { + for (const row of sortedRows) next.delete(row.id); + } else { + for (const row of sortedRows) next.add(row.id); + } + commit(next); + }, [allSelected, sortedRows, selected, commit]); + + const toggleRow = useCallback( + (id: string) => { + const next = new Set(selected); + if (next.has(id)) next.delete(id); + else next.add(id); + commit(next); + }, + [selected, commit], + ); + + return { selected, allSelected, someSelected, toggleAll, toggleRow }; +} diff --git a/src/components/motion/table/utils.ts b/src/components/motion/table/utils.ts new file mode 100644 index 0000000..628251f --- /dev/null +++ b/src/components/motion/table/utils.ts @@ -0,0 +1,33 @@ +import type { ReactNode } from "react"; +import type { TableColumn } from "./types"; + +export const CHECKBOX_PX = 48; +export const CHECKBOX_WIDTH = `${CHECKBOX_PX}px`; + +/** Highlights the top edge of the active column's header cell. */ +export const COLUMN_ACTIVE_SHADOW = "inset 0 1px 0 var(--color-primary)"; + +export function alignFlex(align: TableColumn["align"]) { + if (align === "right") return "justify-end"; + if (align === "center") return "justify-center"; + return "justify-start"; +} + +export function alignText(align: TableColumn["align"]) { + if (align === "right") return "text-right"; + if (align === "center") return "text-center"; + return "text-left"; +} + +export function readCell(row: T, column: TableColumn): ReactNode { + if (column.cell) return column.cell(row); + return (row as Record)[column.key]; +} + +export function readSortValue( + row: T, + column: TableColumn, +): string | number { + if (column.sortValue) return column.sortValue(row); + return (row as Record)[column.key]; +} diff --git a/src/components/motion/tabs.tsx b/src/components/motion/tabs.tsx new file mode 100644 index 0000000..252484c --- /dev/null +++ b/src/components/motion/tabs.tsx @@ -0,0 +1,219 @@ +"use client"; +// beui.dev/components/motion/tabs + +import { + motion, + MotionConfig, + useReducedMotion, + type Transition, +} from "motion/react"; +import { + createContext, + useContext, + useId, + useState, + type ReactNode, +} from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +type Variant = "pill" | "underline" | "segment"; + +type Ctx = { + value: string; + setValue: (v: string) => void; + layoutId: string; + variant: Variant; +}; + +const TabsCtx = createContext(null); + +function useTabs() { + const ctx = useContext(TabsCtx); + if (!ctx) throw new Error("Tabs.* must be used inside "); + return ctx; +} + +// Weighty spring for the active-tab indicator: a touch of overshoot so it +// settles with life instead of snapping. +const transition: Transition = { + type: "spring", + stiffness: 170, + damping: 24, + mass: 1.2, +}; + +export function Tabs({ + defaultValue, + value, + onValueChange, + variant = "pill", + children, + className, +}: { + defaultValue?: string; + value?: string; + onValueChange?: (v: string) => void; + variant?: Variant; + children: ReactNode; + className?: string; +}) { + const [internal, setInternal] = useState(defaultValue ?? ""); + const layoutId = useId(); + const reduce = useReducedMotion(); + const controlled = value !== undefined; + const current = controlled ? value : internal; + const setValue = (v: string) => { + if (!controlled) setInternal(v); + onValueChange?.(v); + }; + return ( + + + {/* layoutRoot: the indicator's layoutId measures in page coordinates, so + inside fixed/scrolled containers it would replay scroll offsets as + movement. The pill only ever travels within the list, so scoping + projection to the Tabs wrapper is always correct. */} + + {children} + + + + ); +} + +const listClasses: Record = { + pill: "inline-flex items-center gap-1 rounded-full bg-card p-1", + underline: "inline-flex items-center gap-1 border-b border-border", + segment: "inline-flex items-center gap-0 rounded-lg bg-card p-0.5", +}; + +export function TabsList({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + const { variant } = useTabs(); + return ( +
+ {children} +
+ ); +} + +export function TabsTrigger({ + value, + children, + className, + indicatorClassName, +}: { + value: string; + children: ReactNode; + className?: string; + indicatorClassName?: string; +}) { + const { value: current, setValue, layoutId, variant } = useTabs(); + const active = current === value; + + if (variant === "underline") { + return ( + + ); + } + + // Pill + Segment use the same trick: a max-contrast pill slides via layoutId, + // text uses `mix-blend-exclusion` so it inverts dynamically against the moving bg. + const radius = variant === "pill" ? "rounded-full" : "rounded-md"; + + return ( +
+ {active ? ( + + ) : null} + +
+ ); +} + +export function TabsContent({ + value, + children, + className, +}: { + value: string; + children: ReactNode; + className?: string; +}) { + const { value: current } = useTabs(); + const reduce = useReducedMotion(); + const active = current === value; + // Inactive panels stay mounted but hidden, so their content (e.g. source + // code) is present in the server-rendered HTML for crawlers and assistive + // tech, instead of being dropped from the DOM. + if (!active) { + return ( + + ); + } + return ( + + {children} + + ); +} diff --git a/src/components/motion/text-cascade.tsx b/src/components/motion/text-cascade.tsx new file mode 100644 index 0000000..7b18ca0 --- /dev/null +++ b/src/components/motion/text-cascade.tsx @@ -0,0 +1,23 @@ +"use client"; +// beui.dev/components/motion/text-animation + +import { ActionSwapText } from "./action-swap"; + +export interface TextCascadeProps { + /** Current text. Changing it cascades the letters to the new value. */ + text: string; + className?: string; +} + +/** + * Letter-by-letter slot roll for standalone text — the old letters drop away + * as the new ones land, left to right. Same motion as the action-swap + * cascade variant, with a text-first API. + */ +export function TextCascade({ text, className }: TextCascadeProps) { + return ( + + {text} + + ); +} diff --git a/src/components/motion/text-reveal.tsx b/src/components/motion/text-reveal.tsx new file mode 100644 index 0000000..1cfafa7 --- /dev/null +++ b/src/components/motion/text-reveal.tsx @@ -0,0 +1,129 @@ +"use client"; +// beui.dev/components/motion/text-animation + +import { + motion, + type Transition, + useInView, + useReducedMotion, +} from "motion/react"; +import { useRef, type ElementType, type ReactNode } from "react"; +import { EASE_OUT } from "src/lib/ease"; +import { cn } from "src/lib/utils"; + +type SplitMode = "word" | "char"; + +export interface TextRevealProps { + text: string | string[]; + as?: ElementType; + className?: string; + split?: SplitMode; + stagger?: number; + delay?: number; + blur?: number; + yOffset?: string | number; + spring?: { stiffness?: number; damping?: number; mass?: number }; + once?: boolean; + whileInView?: boolean; + children?: ReactNode; +} + +const DEFAULT_SPRING = { stiffness: 140, damping: 26, mass: 1.2 }; + +export function TextReveal({ + text, + as: Comp = "span", + className, + split = "word", + stagger = 0.09, + delay = 0, + blur = 12, + yOffset = "40%", + spring, + once = true, + whileInView = false, + children, +}: TextRevealProps) { + const ref = useRef(null); + const inView = useInView(ref, { once, amount: 0.4 }); + const reduce = useReducedMotion(); + const shouldAnimate = whileInView ? inView : true; + + const lines = Array.isArray(text) ? text : [text]; + const springConfig = { ...DEFAULT_SPRING, ...spring }; + + let unitIndex = 0; + const lineCounts = new Map(); + + return ( + + {lines.map((line) => { + const units = split === "word" ? line.split(" ") : Array.from(line); + const lineCount = lineCounts.get(line) ?? 0; + lineCounts.set(line, lineCount + 1); + const lineKey = `${line}-${lineCount}`; + const unitCounts = new Map(); + + return ( + + {units.map((unit, unitIndexInLine) => { + const unitDelay = delay + unitIndex * stagger; + unitIndex += 1; + const unitCount = unitCounts.get(unit) ?? 0; + unitCounts.set(unit, unitCount + 1); + const unitKey = `${unit}-${unitCount}`; + const initial = reduce + ? { opacity: 0 } + : { y: yOffset, opacity: 0, filter: `blur(${blur}px)` }; + const animate = shouldAnimate + ? reduce + ? { opacity: 1 } + : { y: 0, opacity: 1, filter: "blur(0px)" } + : initial; + const transition: Transition = reduce + ? { + opacity: { + duration: 0.25, + ease: EASE_OUT, + delay: unitDelay * 0.3, + }, + } + : { + y: { + type: "spring" as const, + ...springConfig, + delay: unitDelay, + }, + opacity: { + duration: 0.7, + ease: EASE_OUT, + delay: unitDelay, + }, + filter: { + duration: 0.9, + ease: EASE_OUT, + delay: unitDelay, + }, + }; + return ( + + {unit} + {split === "word" && unitIndexInLine < units.length - 1 ? ( +   + ) : null} + + ); + })} + + ); + })} + {children} + + ); +} diff --git a/src/components/motion/text-shimmer.tsx b/src/components/motion/text-shimmer.tsx new file mode 100644 index 0000000..dc23ac2 --- /dev/null +++ b/src/components/motion/text-shimmer.tsx @@ -0,0 +1,34 @@ +import { cn } from "src/lib/utils"; +import type { ElementType, ReactNode } from "react"; + +export interface TextShimmerProps { + children: ReactNode; + as?: ElementType; + duration?: number; + className?: string; +} + +export function TextShimmer({ + children, + as: Comp = "span", + duration = 2.5, + className, +}: TextShimmerProps) { + return ( + <> + + + {children} + + + ); +} diff --git a/src/components/motion/theme-toggle.tsx b/src/components/motion/theme-toggle.tsx new file mode 100644 index 0000000..fa36472 --- /dev/null +++ b/src/components/motion/theme-toggle.tsx @@ -0,0 +1,202 @@ +"use client"; +// beui.dev/components/motion/theme-toggle + +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; +import { useReducedMotion } from "motion/react"; +import { useEffect, useState, type ComponentPropsWithoutRef } from "react"; +import { ActionSwapIcon } from "src/components/motion/action-swap"; +import { cn } from "src/lib/utils"; + +export type ThemeVariant = "rectangle" | "circle" | "circle-blur"; + +export type RectStart = + | "top-left" + | "top-right" + | "bottom-left" + | "bottom-right" + | "center" + | "bottom-up"; + +export interface ThemeToggleProps extends Omit< + ComponentPropsWithoutRef<"button">, + "children" | "onClick" +> { + /** Animation variant. Default: "rectangle". */ + variant?: ThemeVariant; + /** Origin direction for the reveal. Default: "bottom-up". */ + start?: RectStart; + iconClassName?: string; + checked?: boolean; + onCheckedChange?: (checked: boolean) => void; +} + +const VT_STYLE_ID = "beui-theme-toggle-vt"; + +// Duration/easing is component-specific: View Transition API uses CSS, not +// motion springs. 400ms + ease-out mirrors native OS mode-switch timing. +const VT_CSS = ` +html[data-beui-vt="rect"]::view-transition-old(root) { + animation: none; + mix-blend-mode: normal; +} +html[data-beui-vt="rect"]::view-transition-new(root) { + mix-blend-mode: normal; + animation: beui-rect-reveal 400ms ease-out; +} +html[data-beui-vt="circle"]::view-transition-old(root), +html[data-beui-vt="circle-blur"]::view-transition-old(root) { + animation: none; + mix-blend-mode: normal; +} +html[data-beui-vt="circle"]::view-transition-new(root) { + mix-blend-mode: normal; + animation: beui-circle-reveal 700ms cubic-bezier(0.4, 0, 0.2, 1); +} +html[data-beui-vt="circle-blur"]::view-transition-new(root) { + mix-blend-mode: normal; + animation: beui-circle-blur-reveal 700ms cubic-bezier(0.4, 0, 0.2, 1); +} +@keyframes beui-rect-reveal { + from { clip-path: var(--beui-vt-from, inset(100% 0 0 0)); } + to { clip-path: inset(0 0 0 0); } +} +@keyframes beui-circle-reveal { + from { clip-path: circle(0% at var(--beui-vt-origin, 50% 100%)); } + to { clip-path: circle(150% at var(--beui-vt-origin, 50% 100%)); } +} +@keyframes beui-circle-blur-reveal { + from { clip-path: circle(0% at var(--beui-vt-origin, 50% 100%)); filter: blur(8px); } + to { clip-path: circle(150% at var(--beui-vt-origin, 50% 100%)); filter: blur(0px); } +} +`; + +const RECT_FROM: Record = { + "top-left": "inset(0 100% 100% 0)", + "top-right": "inset(0 0 100% 100%)", + "bottom-left": "inset(100% 100% 0 0)", + "bottom-right": "inset(100% 0 0 100%)", + center: "inset(50% 50% 50% 50%)", + "bottom-up": "inset(100% 0 0 0)", +}; + +const CIRCLE_ORIGIN: Record = { + "top-left": "0% 0%", + "top-right": "100% 0%", + "bottom-left": "0% 100%", + "bottom-right": "100% 100%", + center: "50% 50%", + "bottom-up": "50% 100%", +}; + +export function useThemeToggle({ + variant = "rectangle", + start = "bottom-up", + checked, + onCheckedChange, +}: { + variant?: ThemeVariant; + start?: RectStart; + checked?: boolean; + onCheckedChange?: (checked: boolean) => void; +} = {}) { + const { setTheme, resolvedTheme } = useTheme(); + const reduce = useReducedMotion() ?? false; + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + useEffect(() => { + if (document.getElementById(VT_STYLE_ID)) return; + const el = document.createElement("style"); + el.id = VT_STYLE_ID; + el.textContent = VT_CSS; + document.head.appendChild(el); + }, []); + const isControlled = typeof checked === "boolean"; + const isDark = mounted && (isControlled ? checked : resolvedTheme === "dark"); + + const toggle = () => { + const next = isDark ? "light" : "dark"; + const applyTheme = () => { + if (isControlled) { + onCheckedChange?.(next === "dark"); + } else { + setTheme(next); + } + }; + + if (reduce || !("startViewTransition" in document)) { + applyTheme(); + return; + } + + const root = document.documentElement; + + if (variant === "rectangle") { + root.style.setProperty("--beui-vt-from", RECT_FROM[start]); + root.dataset.beuiVt = "rect"; + } else { + root.style.setProperty("--beui-vt-origin", CIRCLE_ORIGIN[start]); + root.dataset.beuiVt = variant; + } + + const vt = ( + document as Document & { + startViewTransition(cb: () => void): { finished: Promise }; + } + ).startViewTransition(applyTheme); + + vt.finished.finally(() => { + delete root.dataset.beuiVt; + }); + }; + + return { isDark, mounted, toggle }; +} + +export function ThemeToggle({ + variant = "rectangle", + start = "bottom-up", + className, + iconClassName, + checked, + onCheckedChange, + ...rest +}: ThemeToggleProps) { + const { isDark, mounted, toggle } = useThemeToggle({ + variant, + start, + checked, + onCheckedChange, + }); + + return ( + + ); +} diff --git a/src/components/motion/tilt-card.tsx b/src/components/motion/tilt-card.tsx new file mode 100644 index 0000000..178e2c2 --- /dev/null +++ b/src/components/motion/tilt-card.tsx @@ -0,0 +1,83 @@ +"use client"; +// beui.dev/components/motion/tilt-card + +import { + motion, + useMotionTemplate, + useMotionValue, + useReducedMotion, + useSpring, +} from "motion/react"; +import { useRef, type ReactNode } from "react"; +import { SPRING_MOUSE } from "src/lib/ease"; +import { useHoverCapable } from "src/lib/hooks/use-hover-capable"; +import { cn } from "src/lib/utils"; + +export interface TiltCardProps { + children: ReactNode; + max?: number; + glare?: boolean; + className?: string; +} + +export function TiltCard({ + children, + max = 12, + glare = true, + className, +}: TiltCardProps) { + const ref = useRef(null); + const reduce = useReducedMotion(); + const canHover = useHoverCapable(); + // Decorative cursor-follow: skip on touch (phantom hover) and reduced motion. + const enabled = !reduce && canHover; + const rx = useMotionValue(0); + const ry = useMotionValue(0); + const gx = useMotionValue(50); + const gy = useMotionValue(50); + + const srx = useSpring(rx, SPRING_MOUSE); + const sry = useSpring(ry, SPRING_MOUSE); + + const onMove = (e: React.MouseEvent) => { + const el = ref.current; + if (!el || !enabled) return; + const rect = el.getBoundingClientRect(); + const px = (e.clientX - rect.left) / rect.width; + const py = (e.clientY - rect.top) / rect.height; + ry.set((px - 0.5) * max); + rx.set((0.5 - py) * max); + gx.set(px * 100); + gy.set(py * 100); + }; + + const onLeave = () => { + rx.set(0); + ry.set(0); + }; + + const transform = useMotionTemplate`perspective(1000px) rotateX(${srx}deg) rotateY(${sry}deg)`; + const glareBg = useMotionTemplate`radial-gradient(circle at ${gx}% ${gy}%, var(--foreground), transparent 50%)`; + + return ( + + {children} + {glare && enabled ? ( + + ) : null} + + ); +} diff --git a/src/components/motion/tooltip.tsx b/src/components/motion/tooltip.tsx new file mode 100644 index 0000000..3f6c100 --- /dev/null +++ b/src/components/motion/tooltip.tsx @@ -0,0 +1,245 @@ +"use client"; +// beui.dev/components/motion/tooltip + +import { + AnimatePresence, + motion, + useReducedMotion, + type Variants, +} from "motion/react"; +import { + cloneElement, + isValidElement, + useEffect, + useId, + useRef, + useState, + type ReactElement, + type ReactNode, +} from "react"; +import { createPortal } from "react-dom"; +import { EASE_OUT } from "src/lib/ease"; +import { useHoverCapable } from "src/lib/hooks/use-hover-capable"; +import { cn } from "src/lib/utils"; + +type Side = "top" | "right" | "bottom" | "left"; + +export interface TooltipProps { + content: ReactNode; + children: ReactElement; + side?: Side; + /** Delay before showing (ms). Default 120. */ + delay?: number; + className?: string; + /** Classes for the outer wrapper span. Use to fix baseline / fill parent. */ + wrapperClassName?: string; +} + +type TooltipPosition = { + top: number; + left: number; + transform: string; +}; + +const transformOrigin: Record = { + top: "center bottom", + bottom: "center top", + left: "right center", + right: "left center", +}; + +// Offset is in the direction *away* from the trigger — content originates near +// the trigger and rises into resting position. +const offsetFrom: Record = { + top: { y: 10 }, + bottom: { y: -10 }, + left: { x: 10 }, + right: { x: -10 }, +}; + +function buildVariants(side: Side): Variants { + const offset = offsetFrom[side]; + return { + initial: { + opacity: 0, + scale: 0.85, + filter: "blur(10px)", + x: offset.x ?? 0, + y: offset.y ?? 0, + }, + animate: { + opacity: 1, + scale: 1, + filter: "blur(0px)", + x: 0, + y: 0, + transition: { + type: "spring", + stiffness: 380, + damping: 30, + mass: 0.7, + opacity: { duration: 0.22, ease: EASE_OUT }, + filter: { duration: 0.3, ease: EASE_OUT }, + }, + }, + exit: { + opacity: 0, + scale: 0.92, + filter: "blur(6px)", + x: (offset.x ?? 0) * 0.6, + y: (offset.y ?? 0) * 0.6, + transition: { duration: 0.14, ease: EASE_OUT }, + }, + }; +} + +const REDUCED_VARIANTS: Variants = { + initial: { opacity: 0 }, + animate: { opacity: 1, transition: { duration: 0.14, ease: EASE_OUT } }, + exit: { opacity: 0, transition: { duration: 0.1, ease: EASE_OUT } }, +}; + +// Once any tooltip has just closed, neighbouring tooltips open without the +// initial delay — moving along a toolbar feels instant after the first one. +const WARM_WINDOW_MS = 300; +let lastHiddenAt = 0; + +export function Tooltip({ + content, + children, + side = "top", + delay = 120, + className, + wrapperClassName, +}: TooltipProps) { + const [open, setOpen] = useState(false); + const [position, setPosition] = useState(null); + const id = useId(); + const wrapperRef = useRef(null); + const timer = useRef | null>(null); + const reduce = useReducedMotion(); + const canHover = useHoverCapable(); + + useEffect(() => { + if (!open) return; + + const updatePosition = () => { + const node = wrapperRef.current; + if (!node) return; + + const rect = node.getBoundingClientRect(); + const gap = 8; + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + const next: Record = { + top: { + top: rect.top - gap, + left: centerX, + transform: "translate(-50%, -100%)", + }, + right: { + top: centerY, + left: rect.right + gap, + transform: "translate(0, -50%)", + }, + bottom: { + top: rect.bottom + gap, + left: centerX, + transform: "translate(-50%, 0)", + }, + left: { + top: centerY, + left: rect.left - gap, + transform: "translate(-100%, -50%)", + }, + }; + + setPosition(next[side]); + }; + + updatePosition(); + window.addEventListener("resize", updatePosition); + window.addEventListener("scroll", updatePosition, true); + + return () => { + window.removeEventListener("resize", updatePosition); + window.removeEventListener("scroll", updatePosition, true); + }; + }, [open, side]); + + const show = () => { + if (!canHover) return; + if (timer.current) clearTimeout(timer.current); + const warm = Date.now() - lastHiddenAt < WARM_WINDOW_MS; + timer.current = setTimeout(() => setOpen(true), warm ? 0 : delay); + }; + const hide = () => { + if (timer.current) { + clearTimeout(timer.current); + timer.current = null; + } + if (open) lastHiddenAt = Date.now(); + setOpen(false); + }; + + if (!isValidElement(children)) return children; + + const trigger = cloneElement( + children as ReactElement>, + { + onMouseEnter: show, + onMouseLeave: hide, + onFocus: show, + onBlur: hide, + "aria-describedby": id, + }, + ); + + const variants = reduce ? REDUCED_VARIANTS : buildVariants(side); + + return ( + + {trigger} + {typeof document === "undefined" + ? null + : createPortal( + + {open && position ? ( + + + {content} + + + ) : null} + , + document.body, + )} + + ); +} diff --git a/src/lib/ease.ts b/src/lib/ease.ts new file mode 100644 index 0000000..bfc0f6a --- /dev/null +++ b/src/lib/ease.ts @@ -0,0 +1,45 @@ +export const EASE_OUT = [0.16, 1, 0.3, 1] as const; +export const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const; +export const EASE_DRAWER = [0.32, 0.72, 0, 1] as const; + +/** CSS string form of EASE_OUT for inline style transitions. */ +export const EASE_OUT_CSS = "cubic-bezier(0.16, 1, 0.3, 1)"; + +/** Press feedback on buttons and other tappable surfaces. */ +export const SPRING_PRESS = { + type: "spring", + stiffness: 500, + damping: 30, + mass: 0.6, +} as const; + +/** Content swaps — label/icon slots trading places inside a control. */ +export const SPRING_SWAP = { + type: "spring", + stiffness: 460, + damping: 30, + mass: 0.55, +} as const; + +/** Overlay panel entrances — modals and sheets summoned by pointer. */ +export const SPRING_PANEL = { + type: "spring", + stiffness: 420, + damping: 40, + mass: 0.5, +} as const; + +/** Shared-layout glides — pills, indicators and panels morphing between positions. */ +export const SPRING_LAYOUT = { + type: "spring", + stiffness: 360, + damping: 32, + mass: 0.6, +} as const; + +/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */ +export const SPRING_MOUSE = { + stiffness: 200, + damping: 15, + mass: 0.3, +} as const; diff --git a/src/lib/hooks/use-hover-capable.ts b/src/lib/hooks/use-hover-capable.ts new file mode 100644 index 0000000..8ad9a4a --- /dev/null +++ b/src/lib/hooks/use-hover-capable.ts @@ -0,0 +1,28 @@ +"use client"; + +import { useEffect, useState } from "react"; + +/** + * Returns true only on devices that have a true hover (mouse / trackpad). + * Touch devices fire phantom `:hover` on tap that sticks until tap-elsewhere + * — gate hover-only effects (scale lifts, magnetic pulls) behind this. + */ +export function useHoverCapable() { + const [canHover, setCanHover] = useState(false); + + useEffect(() => { + if (typeof window === "undefined" || !window.matchMedia) return undefined; + const mediaQuery = window.matchMedia("(hover: hover) and (pointer: fine)"); + /** Syncs component state with the current hover-capable media query. */ + const update = () => { + setCanHover(mediaQuery.matches); + }; + update(); + mediaQuery.addEventListener?.("change", update); + return () => { + mediaQuery.removeEventListener?.("change", update); + }; + }, []); + + return canHover; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..7f81182 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,7 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +/** Merges conditional class names and resolves Tailwind utility conflicts. */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 27f3b8c..78ff536 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,24 +1,4 @@ -import type { Config } from "tailwindcss"; - export default { - content: ["./entrypoints/**/*.{html,tsx,ts}"], - theme: { - extend: { - colors: { - primary: { - 50: "#eff6ff", - 100: "#dbeafe", - 200: "#bfdbfe", - 300: "#93c5fd", - 400: "#60a5fa", - 500: "#3b82f6", - 600: "#2563eb", - 700: "#1d4ed8", - 800: "#1e40af", - 900: "#1e3a8a", - }, - }, - }, - }, - plugins: [], -} satisfies Config; + content: ["./entrypoints/**/*.{html,tsx,ts}", "./src/**/*.{html,tsx,ts}"], + darkMode: "class", +}; diff --git a/tests/lib/i18n.test.ts b/tests/lib/i18n.test.ts new file mode 100644 index 0000000..dcd8b4c --- /dev/null +++ b/tests/lib/i18n.test.ts @@ -0,0 +1,67 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, it, expect, vi, afterEach } from "vitest"; +import { t } from "../../lib/i18n"; + +describe("t", () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + }); + + it("returns the localized message when browser i18n has one", () => { + const getMessage = vi.fn().mockReturnValue("Hello"); + vi.stubGlobal("browser", { + i18n: { getMessage }, + }); + + expect(t("greeting")).toBe("Hello"); + expect(getMessage.mock.calls[0]).toEqual(["greeting", undefined]); + }); + + it("passes substitutions through to browser i18n", () => { + const getMessage = vi.fn().mockReturnValue("Hello Alice"); + vi.stubGlobal("browser", { + i18n: { getMessage }, + }); + + expect(t("greeting", "Alice")).toBe("Hello Alice"); + expect(getMessage).toHaveBeenCalledWith("greeting", "Alice"); + }); + + it("falls back to the message key when browser i18n returns an empty value", () => { + vi.stubGlobal("browser", { + i18n: { getMessage: vi.fn().mockReturnValue("") }, + }); + + expect(t("missingMessage")).toBe("missingMessage"); + }); + + it("falls back to the message key when browser i18n throws", () => { + vi.stubGlobal("browser", { + i18n: { + getMessage: vi.fn(() => { + throw new Error("i18n unavailable"); + }), + }, + }); + + expect(t("safeFallback")).toBe("safeFallback"); + }); + + it("keeps all locale message key sets aligned with English", () => { + const localesDir = join(process.cwd(), "public", "_locales"); + const readMessages = (locale: string) => + JSON.parse( + readFileSync(join(localesDir, locale, "messages.json"), "utf8"), + ) as Record; + const englishKeys = Object.keys(readMessages("en")).sort(); + + for (const locale of readdirSync(localesDir)) { + if (locale === "en") continue; + + const localeKeys = Object.keys(readMessages(locale)).sort(); + expect(localeKeys, `${locale} keys should match en`).toEqual(englishKeys); + } + }); +}); diff --git a/tests/popup/components/Button.test.tsx b/tests/popup/components/Button.test.tsx new file mode 100644 index 0000000..61977fc --- /dev/null +++ b/tests/popup/components/Button.test.tsx @@ -0,0 +1,64 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import Button from "../../../entrypoints/popup/components/Button"; + +describe("Button", () => { + it("renders children", () => { + render(); + expect(screen.getByText("Click me")).toBeInTheDocument(); + }); + + it("calls onClick when clicked", () => { + const onClick = vi.fn(); + render(); + fireEvent.click(screen.getByRole("button")); + expect(onClick).toHaveBeenCalledOnce(); + }); + + it("does not call onClick when disabled", () => { + const onClick = vi.fn(); + render( + , + ); + fireEvent.click(screen.getByRole("button")); + expect(onClick).not.toHaveBeenCalled(); + }); + + it("is disabled when disabled prop is true", () => { + render(); + expect(screen.getByRole("button")).toBeDisabled(); + }); + + it("renders icon when provided", () => { + render(); + expect(screen.getByTestId("icon")).toBeInTheDocument(); + }); + + it("applies w-full class when fullWidth is true", () => { + render(); + expect(screen.getByRole("button")).toHaveClass("w-full"); + }); + + it.each(["primary", "secondary", "danger", "success"] as const)( + "renders %s variant without error", + (variant) => { + render(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }, + ); + + it.each(["sm", "md", "lg"] as const)( + "renders %s size without error", + (size) => { + render(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }, + ); + + it("applies opacity-50 when disabled", () => { + render(); + expect(screen.getByRole("button")).toHaveClass("opacity-50"); + }); +}); diff --git a/tests/popup/components/GmailTricks.test.tsx b/tests/popup/components/GmailTricks.test.tsx new file mode 100644 index 0000000..93a6126 --- /dev/null +++ b/tests/popup/components/GmailTricks.test.tsx @@ -0,0 +1,108 @@ +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { afterEach, beforeEach, describe, it, expect, vi } from "vitest"; +import GmailTricks from "../../../entrypoints/popup/components/GmailTricks"; + +describe("GmailTricks", () => { + beforeEach(() => { + const messages: Record = { + allCombos: "All Combos", + copy: "Copy", + dotPlus: "Dot + Plus", + dotTrick: "Dot Trick", + generateTricks: "Generate Tricks", + generatedVariations: "Generated Variations", + gmailTrickInfo: + "Dots are ignored and everything after + goes to the same inbox", + gmailTrickInfoLabel: "Gmail trick:", + googlemail: "Googlemail", + numberOfVariations: "Number of variations", + plusTags: "Plus (+) Tags", + random: "Random", + randomizeDotPositions: "Randomize dot positions", + removeDots: "Remove Dots", + sequential: "Sequential", + totalCount: "$1 total", + }; + + vi.stubGlobal("browser", { + i18n: { + getMessage: vi.fn((key: string, substitutions?: string | string[]) => { + const message = messages[key] ?? key; + const first = Array.isArray(substitutions) + ? substitutions[0] + : substitutions; + return first ? message.replace("$1", first) : message; + }), + }, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + }); + + it("generates a fallback dot result for a one-character username", async () => { + const onCopy = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: /generate tricks/i })); + + await waitFor(() => { + expect(screen.getByText("a@gmail.com")).toBeInTheDocument(); + }); + expect(onCopy).toHaveBeenCalledWith("a@gmail.com"); + }); + + it("generates googlemail variation for a one-character username", async () => { + const onCopy = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: /googlemail/i })); + fireEvent.click(screen.getByRole("button", { name: /generate tricks/i })); + + await waitFor(() => { + expect(screen.getByText("a@googlemail.com")).toBeInTheDocument(); + }); + expect(onCopy).toHaveBeenCalledWith("a@googlemail.com"); + }); + + it("generates plus tags for a one-character username", async () => { + const onCopy = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: /plus \(\+\) tags/i })); + fireEvent.click(screen.getByRole("button", { name: /generate tricks/i })); + + await waitFor(() => { + expect(screen.getByText("a+newsletter@gmail.com")).toBeInTheDocument(); + }); + expect(onCopy).toHaveBeenCalledWith("a+newsletter@gmail.com"); + }); + + it("generates dot plus aliases for a one-character username", async () => { + const onCopy = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: /dot \+ plus/i })); + fireEvent.click(screen.getByRole("button", { name: /generate tricks/i })); + + await waitFor(() => { + expect(screen.getByText("a+shop@gmail.com")).toBeInTheDocument(); + }); + expect(onCopy).toHaveBeenCalledWith("a+shop@gmail.com"); + }); + + it("generates combo aliases for a one-character username", async () => { + const onCopy = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: /all combos/i })); + fireEvent.click(screen.getByRole("button", { name: /generate tricks/i })); + + await waitFor(() => { + expect(screen.getByText("a+newsletter@gmail.com")).toBeInTheDocument(); + }); + expect(onCopy).toHaveBeenCalledWith("a+newsletter@gmail.com"); + }); +}); diff --git a/tests/popup/components/Input.test.tsx b/tests/popup/components/Input.test.tsx new file mode 100644 index 0000000..ef243e4 --- /dev/null +++ b/tests/popup/components/Input.test.tsx @@ -0,0 +1,63 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import Input from "../../../entrypoints/popup/components/Input"; + +describe("Input", () => { + it("renders with label", () => { + render(); + expect(screen.getByText("Email")).toBeInTheDocument(); + }); + + it("renders without label when not provided", () => { + const { container } = render(); + expect(container.querySelector("label")).not.toBeInTheDocument(); + }); + + it("shows placeholder text", () => { + render(); + expect(screen.getByPlaceholderText("Enter email")).toBeInTheDocument(); + }); + + it("calls onChange with new value", () => { + const onChange = vi.fn(); + render(); + fireEvent.change(screen.getByRole("textbox"), { + target: { value: "test@gmail.com" }, + }); + expect(onChange).toHaveBeenCalledWith("test@gmail.com"); + }); + + it("is disabled when disabled prop is true", () => { + render(); + expect(screen.getByRole("textbox")).toBeDisabled(); + }); + + it("has disabled styles when disabled", () => { + render(); + expect(screen.getByRole("textbox")).toHaveClass("disabled:opacity-50"); + }); + + it("calls onKeyPress on key events", () => { + const onKeyPress = vi.fn(); + render(); + fireEvent.keyPress(screen.getByRole("textbox"), { + key: "Enter", + code: "Enter", + charCode: 13, + }); + expect(onKeyPress).toHaveBeenCalled(); + }); + + it("displays current value", () => { + render(); + expect(screen.getByRole("textbox")).toHaveValue("hello"); + }); + + it("renders email type input correctly", () => { + render(); + expect(screen.getByDisplayValue("a@b.com")).toHaveAttribute( + "type", + "email", + ); + }); +}); diff --git a/tests/popup/components/Toggle.test.tsx b/tests/popup/components/Toggle.test.tsx new file mode 100644 index 0000000..aa7e1b7 --- /dev/null +++ b/tests/popup/components/Toggle.test.tsx @@ -0,0 +1,61 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import Toggle from "../../../entrypoints/popup/components/Toggle"; + +describe("Toggle", () => { + it("renders label", () => { + render(); + expect(screen.getByText("Dark mode")).toBeInTheDocument(); + }); + + it("renders description when provided", () => { + render( + , + ); + expect(screen.getByText("Some description")).toBeInTheDocument(); + }); + + it("does not render description when omitted", () => { + render(); + expect(screen.queryByText("Some description")).not.toBeInTheDocument(); + }); + + it("calls onChange with true when toggled from off", () => { + const onChange = vi.fn(); + render(); + fireEvent.click(screen.getByRole("switch")); + expect(onChange).toHaveBeenCalledWith(true); + }); + + it("calls onChange with false when toggled from on", () => { + const onChange = vi.fn(); + render(); + fireEvent.click(screen.getByRole("switch")); + expect(onChange).toHaveBeenCalledWith(false); + }); + + it("has aria-checked=true when enabled", () => { + render(); + expect(screen.getByRole("switch")).toHaveAttribute("aria-checked", "true"); + }); + + it("has aria-checked=false when disabled", () => { + render(); + expect(screen.getByRole("switch")).toHaveAttribute("aria-checked", "false"); + }); + + it("has bg-primary when enabled", () => { + render(); + expect(screen.getByRole("switch")).toHaveClass("bg-primary"); + }); + + it("has bg-muted when disabled", () => { + render(); + expect(screen.getByRole("switch")).toHaveClass("bg-muted"); + }); +}); diff --git a/tests/popup/utils.test.ts b/tests/popup/utils.test.ts new file mode 100644 index 0000000..13972a6 --- /dev/null +++ b/tests/popup/utils.test.ts @@ -0,0 +1,382 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { + getAccountStorageKey, + getLegacyAccountStorageKey, + generateAlias, + generateRandomString, + validateEmail, + generateDotVariations, + getDotVariationCandidates, + filterAliases, +} from "../../entrypoints/popup/utils"; + +// ─── getAccountStorageKey ──────────────────────────────────────────────────── + +describe("getAccountStorageKey", () => { + it("encodes the email so it round-trips uniquely", () => { + expect(getAccountStorageKey("user@gmail.com", "history")).toBe( + "history_user%40gmail.com", + ); + }); + + it("handles dots in username", () => { + expect(getAccountStorageKey("first.last@gmail.com", "stats")).toBe( + "stats_first.last%40gmail.com", + ); + }); + + it("handles plus signs", () => { + expect(getAccountStorageKey("user+tag@gmail.com", "fav")).toBe( + "fav_user%2Btag%40gmail.com", + ); + }); + + it("handles googlemail domain", () => { + expect(getAccountStorageKey("user@googlemail.com", "history")).toBe( + "history_user%40googlemail.com", + ); + }); + + it("preserves alphanumeric chars", () => { + expect(getAccountStorageKey("abc123@test.com", "key")).toBe( + "key_abc123%40test.com", + ); + }); + + it("does not collide for emails that would collide under the old sanitizer", () => { + const dottedKey = getAccountStorageKey("user.name@gmail.com", "history"); + const underscoreKey = getAccountStorageKey( + "user_name@gmail.com", + "history", + ); + expect(dottedKey).not.toBe(underscoreKey); + }); + + it("normalizes case so the same account always maps to the same key", () => { + expect(getAccountStorageKey("User@Gmail.com", "history")).toBe( + getAccountStorageKey("user@gmail.com", "history"), + ); + }); +}); + +describe("getLegacyAccountStorageKey", () => { + it("keeps the old sanitized format for migration lookups", () => { + expect(getLegacyAccountStorageKey("user.name@gmail.com", "history")).toBe( + "history_user_name_gmail_com", + ); + }); + + it("does not trim or lowercase legacy keys", () => { + expect(getLegacyAccountStorageKey(" User@Gmail.com ", "stats")).toBe( + "stats__User_Gmail_com_", + ); + }); +}); + +// ─── generateAlias ─────────────────────────────────────────────────────────── + +describe("generateAlias", () => { + it("generates alias with plus tag", () => { + expect(generateAlias("user@gmail.com", "shopping")).toBe( + "user+shopping@gmail.com", + ); + }); + + it("returns null for missing @ sign", () => { + expect(generateAlias("notanemail", "tag")).toBeNull(); + }); + + it("returns null for email with more than one @ sign", () => { + expect(generateAlias("user@gmail.com@example.com", "tag")).toBeNull(); + }); + + it("returns null for empty string", () => { + expect(generateAlias("", "tag")).toBeNull(); + }); + + it("trims surrounding whitespace from the base email", () => { + expect(generateAlias(" user@gmail.com ", "tag")).toBe("user+tag@gmail.com"); + }); + + it("preserves dots in username", () => { + expect(generateAlias("first.last@gmail.com", "work")).toBe( + "first.last+work@gmail.com", + ); + }); + + it("handles googlemail domain", () => { + expect(generateAlias("user@googlemail.com", "test")).toBe( + "user+test@googlemail.com", + ); + }); + + it("handles hyphenated tags", () => { + expect(generateAlias("user@gmail.com", "private-mail-abc1")).toBe( + "user+private-mail-abc1@gmail.com", + ); + }); +}); + +// ─── generateRandomString ──────────────────────────────────────────────────── + +describe("generateRandomString", () => { + beforeEach(() => { + vi.spyOn(Math, "random").mockReturnValue(0); + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("private-mail format starts with private-mail- and has 4 chars", () => { + const result = generateRandomString("private-mail"); + expect(result).toMatch(/^private-mail-[a-z0-9]{4}$/); + }); + + it("alphanumeric format has exactly 8 chars", () => { + const result = generateRandomString("alphanumeric"); + expect(result).toMatch(/^[a-z0-9]{8}$/); + }); + + it("words format matches adj-noun-num pattern", () => { + const result = generateRandomString("words"); + expect(result).toMatch(/^[a-z]+-[a-z]+-\d+$/); + }); + + it("timestamp format is a non-empty base36 string", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2024-01-01T00:00:00Z")); + const result = generateRandomString("timestamp", 0); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(0); + vi.useRealTimers(); + }); + + it("timestamp format uses index offset for uniqueness", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2024-01-01T00:00:00Z")); + const first = generateRandomString("timestamp", 0); + const second = generateRandomString("timestamp", 1); + expect(first).not.toBe(second); + vi.useRealTimers(); + }); +}); + +// ─── validateEmail ─────────────────────────────────────────────────────────── + +describe("validateEmail", () => { + it("rejects empty string", () => { + expect(validateEmail("")).toMatchObject({ + isValid: false, + error: "Email is required", + }); + }); + + it("rejects whitespace-only string", () => { + expect(validateEmail(" ")).toMatchObject({ + isValid: false, + error: "Email is required", + }); + }); + + it("rejects email without @", () => { + expect(validateEmail("notanemail")).toMatchObject({ + isValid: false, + error: "Please enter a valid email address", + }); + }); + + it("rejects invalid format (no domain)", () => { + expect(validateEmail("user@")).toMatchObject({ isValid: false }); + }); + + it("rejects domain without dot", () => { + expect(validateEmail("user@localhost")).toMatchObject({ isValid: false }); + }); + + it("accepts valid gmail address", () => { + expect(validateEmail("user@gmail.com")).toMatchObject({ isValid: true }); + }); + + it("accepts a one-character gmail username", () => { + expect(validateEmail("a@gmail.com")).toMatchObject({ isValid: true }); + }); + + it("trims surrounding whitespace before validation", () => { + expect(validateEmail(" user@gmail.com ")).toMatchObject({ isValid: true }); + }); + + it("rejects email with more than one @ sign", () => { + expect(validateEmail("user@gmail.com@example.com")).toMatchObject({ + isValid: false, + }); + }); + + it("accepts googlemail domain", () => { + expect(validateEmail("user@googlemail.com")).toMatchObject({ + isValid: true, + }); + }); + + it("accepts dotted gmail username", () => { + expect(validateEmail("first.last@gmail.com")).toMatchObject({ + isValid: true, + }); + }); + + it("warns but allows non-gmail addresses", () => { + const result = validateEmail("user@yahoo.com"); + expect(result.isValid).toBe(true); + expect(result.warning).toContain("⚠️"); + }); +}); + +// ─── generateDotVariations ─────────────────────────────────────────────────── + +describe("generateDotVariations", () => { + it("returns empty array for single-char username", () => { + expect(generateDotVariations("a")).toEqual([]); + }); + + it("generates sequential single-dot variations", () => { + const result = generateDotVariations("abc", 10, false); + expect(result).toContain("a.bc"); + expect(result).toContain("ab.c"); + }); + + it("generates double-dot variations for 4+ char usernames", () => { + const result = generateDotVariations("abcd", 20, false); + const doubleDot = result.filter( + (v: string) => (v.match(/\./g) || []).length === 2, + ); + expect(doubleDot.length).toBeGreaterThan(0); + }); + + it("respects count limit", () => { + const result = generateDotVariations("abcdefghij", 3, false); + expect(result.length).toBeLessThanOrEqual(3); + }); + + it("deduplicates results", () => { + const result = generateDotVariations("abcd", 20, false); + const unique = new Set(result); + expect(unique.size).toBe(result.length); + }); + + it("all variations contain the original chars (no additions)", () => { + const result = generateDotVariations("abc", 10, false); + result.forEach((v: string) => { + expect(v.replace(/\./g, "")).toBe("abc"); + }); + }); + + it("random mode returns requested count", () => { + const result = generateDotVariations("abcdefg", 5, true); + expect(result.length).toBeLessThanOrEqual(5); + }); +}); + +describe("getDotVariationCandidates", () => { + it("returns dot variations when they exist", () => { + expect(getDotVariationCandidates("abc", 10, false)).toEqual([ + "a.bc", + "ab.c", + ]); + }); + + it("falls back to the original username for a one-character username", () => { + expect(getDotVariationCandidates("a", 10, false)).toEqual(["a"]); + }); +}); + +// ─── filterAliases ─────────────────────────────────────────────────────────── + +describe("filterAliases", () => { + const aliases = [ + { email: "user+shopping@gmail.com", timestamp: 3000 }, + { email: "user+work@gmail.com", timestamp: 2000 }, + { email: "user+spam@gmail.com", timestamp: 1000 }, + ]; + + it("returns all aliases with no filters", () => { + const result = filterAliases(aliases, { + viewMode: "all", + favorites: [], + searchQuery: "", + filterTag: "all", + sortBy: "recent", + }); + expect(result).toHaveLength(3); + }); + + it("filters by search query (case-insensitive)", () => { + const result = filterAliases(aliases, { + viewMode: "all", + favorites: [], + searchQuery: "SHOPPING", + filterTag: "all", + sortBy: "recent", + }); + expect(result).toHaveLength(1); + expect(result[0].email).toBe("user+shopping@gmail.com"); + }); + + it("filters by tag", () => { + const result = filterAliases(aliases, { + viewMode: "all", + favorites: [], + searchQuery: "", + filterTag: "work", + sortBy: "recent", + }); + expect(result).toHaveLength(1); + expect(result[0].email).toBe("user+work@gmail.com"); + }); + + it("filters favorites only", () => { + const result = filterAliases(aliases, { + viewMode: "favorites", + favorites: ["user+work@gmail.com"], + searchQuery: "", + filterTag: "all", + sortBy: "recent", + }); + expect(result).toHaveLength(1); + expect(result[0].email).toBe("user+work@gmail.com"); + }); + + it("sorts by recent (descending timestamp)", () => { + const result = filterAliases(aliases, { + viewMode: "all", + favorites: [], + searchQuery: "", + filterTag: "all", + sortBy: "recent", + }); + expect(result[0].email).toBe("user+shopping@gmail.com"); + expect(result[2].email).toBe("user+spam@gmail.com"); + }); + + it("sorts alphabetically", () => { + const result = filterAliases(aliases, { + viewMode: "all", + favorites: [], + searchQuery: "", + filterTag: "all", + sortBy: "alphabetical", + }); + expect(result[0].email).toBe("user+shopping@gmail.com"); + expect(result[1].email).toBe("user+spam@gmail.com"); + expect(result[2].email).toBe("user+work@gmail.com"); + }); + + it("returns empty when search matches nothing", () => { + const result = filterAliases(aliases, { + viewMode: "all", + favorites: [], + searchQuery: "xyz-no-match", + filterTag: "all", + sortBy: "recent", + }); + expect(result).toHaveLength(0); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..dffa9b9 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,25 @@ +import "@testing-library/jest-dom"; +import { vi } from "vitest"; + +Object.defineProperty(globalThis, "browser", { + value: { + storage: { + local: { + get: vi.fn().mockResolvedValue({}), + set: vi.fn(() => Promise.resolve()), + onChanged: { + addListener: vi.fn(), + removeListener: vi.fn(), + }, + }, + }, + }, + writable: true, + configurable: true, +}); + +Object.defineProperty(navigator, "clipboard", { + value: { writeText: vi.fn(() => Promise.resolve()) }, + writable: true, + configurable: true, +}); diff --git a/tsconfig.json b/tsconfig.json index 9b364da..c3c0df4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,17 @@ "extends": "./.wxt/tsconfig.json", "compilerOptions": { "allowImportingTsExtensions": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "paths": { + "@": ["."], + "@/*": ["./*"], + "~": ["."], + "~/*": ["./*"], + "@@": ["."], + "@@/*": ["./*"], + "~~": ["."], + "~~/*": ["./*"], + "src/*": ["./src/*"] + } } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..69be5ba --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; +import { fileURLToPath } from "node:url"; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + src: fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + test: { + environment: "jsdom", + globals: true, + setupFiles: ["./tests/setup.ts"], + exclude: ["node_modules/**", ".wxt/**", ".output/**"], + include: ["**/*.test.{ts,tsx}"], + pool: "threads", + }, +}); diff --git a/wxt.config.ts b/wxt.config.ts index c2d68f8..05b01c3 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -1,15 +1,30 @@ import { defineConfig } from "wxt"; import { EventEmitter } from "events"; +import { fileURLToPath } from "node:url"; // Fix EventEmitter maxListeners warning EventEmitter.defaultMaxListeners = 15; export default defineConfig({ modules: ["@wxt-dev/module-react", "@wxt-dev/auto-icons"], + vite: () => ({ + define: { + "process.emit": "(() => {})", + "process.env": "{}", + }, + resolve: { + alias: { + src: fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + }), manifest: { - name: "Gmail Alias Toolkit", - description: - "Generate and manage Gmail aliases with plus addressing and presets", + name: "__MSG_extensionName__", + description: "__MSG_extensionDescription__", + default_locale: "en", + action: { + default_title: "__MSG_extensionName__", + }, permissions: ["storage", "clipboardWrite", "contextMenus"], host_permissions: [""], browser_specific_settings: { @@ -21,7 +36,7 @@ export default defineConfig({ }, }, }, - } as any, + }, autoIcons: { developmentIndicator: "overlay", }, diff --git a/yarn.lock b/yarn.lock index b9e2255..8888f9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,3486 +1,6449 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@1natsu/wait-element@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@1natsu/wait-element/-/wait-element-4.1.2.tgz#f18b031dc9fb4ac22e114ed04daeef1fbde2217f" - integrity sha512-qWxSJD+Q5b8bKOvESFifvfZ92DuMsY+03SBNjTO34ipJLP6mZ9yK4bQz/vlh48aEQXoJfaZBqUwKL5BdI5iiWw== - dependencies: - defu "^6.1.4" - many-keys-map "^2.0.1" - -"@aklinker1/rollup-plugin-visualizer@5.12.0": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@aklinker1/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz#259d7ab48248eaea6439b7b281a5d1b262c21bb6" - integrity sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ== - dependencies: - open "^8.4.0" - picomatch "^2.3.1" - source-map "^0.7.4" - yargs "^17.5.1" - -"@alloc/quick-lru@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" - integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== - -"@babel/code-frame@^7.21.4": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/code-frame@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" - integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== - dependencies: - "@babel/helper-validator-identifier" "^7.29.7" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" - integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== - -"@babel/core@^7.28.5": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" - integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.7" - "@babel/helper-compilation-targets" "^7.29.7" - "@babel/helper-module-transforms" "^7.29.7" - "@babel/helpers" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/template" "^7.29.7" - "@babel/traverse" "^7.29.7" - "@babel/types" "^7.29.7" - "@jridgewell/remapping" "^2.3.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3" - integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ== - dependencies: - "@babel/parser" "^7.29.7" - "@babel/types" "^7.29.7" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042" - integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g== - dependencies: - "@babel/compat-data" "^7.29.7" - "@babel/helper-validator-option" "^7.29.7" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-globals@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b" - integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA== - -"@babel/helper-module-imports@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396" - integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g== - dependencies: - "@babel/traverse" "^7.29.7" - "@babel/types" "^7.29.7" - -"@babel/helper-module-transforms@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae" - integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg== - dependencies: - "@babel/helper-module-imports" "^7.29.7" - "@babel/helper-validator-identifier" "^7.29.7" - "@babel/traverse" "^7.29.7" - -"@babel/helper-plugin-utils@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-string-parser@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" - integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== - -"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" - integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== - -"@babel/helper-validator-identifier@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" - integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== - -"@babel/helper-validator-option@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a" - integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw== - -"@babel/helpers@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607" - integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg== - dependencies: - "@babel/template" "^7.29.7" - "@babel/types" "^7.29.7" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.4": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" - integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== - dependencies: - "@babel/types" "^7.28.5" - -"@babel/parser@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334" - integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== - dependencies: - "@babel/types" "^7.29.7" - -"@babel/plugin-transform-react-jsx-self@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" - integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-react-jsx-source@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" - integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/runtime@7.28.2": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473" - integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA== - -"@babel/template@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" - integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/types" "^7.29.7" - -"@babel/traverse@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d" - integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.7" - "@babel/helper-globals" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/template" "^7.29.7" - "@babel/types" "^7.29.7" - debug "^4.3.1" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.4", "@babel/types@^7.28.2", "@babel/types@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" - integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.28.5" - -"@babel/types@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" - integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== - dependencies: - "@babel/helper-string-parser" "^7.29.7" - "@babel/helper-validator-identifier" "^7.29.7" - -"@devicefarmer/adbkit-logcat@^2.1.2": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@devicefarmer/adbkit-logcat/-/adbkit-logcat-2.1.3.tgz#c7a1fb58e500f5799711f32906a2210c0d1ac5ac" - integrity sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw== - -"@devicefarmer/adbkit-monkey@~1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@devicefarmer/adbkit-monkey/-/adbkit-monkey-1.2.1.tgz#28cd6a121c5d572588081dd1c53454c604eef241" - integrity sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg== - -"@devicefarmer/adbkit@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@devicefarmer/adbkit/-/adbkit-3.3.8.tgz#04bc35cb3a2d7385f2e22de40f54162088083183" - integrity sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw== - dependencies: - "@devicefarmer/adbkit-logcat" "^2.1.2" - "@devicefarmer/adbkit-monkey" "~1.2.1" - bluebird "~3.7" - commander "^9.1.0" - debug "~4.3.1" - node-forge "^1.3.1" - split "~1.0.1" - -"@emnapi/runtime@^1.7.0": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.7.1.tgz#a73784e23f5d57287369c808197288b52276b791" - integrity sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA== - dependencies: - tslib "^2.4.0" - -"@esbuild/aix-ppc64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c" - integrity sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw== - -"@esbuild/android-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz#61ea550962d8aa12a9b33194394e007657a6df57" - integrity sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA== - -"@esbuild/android-arm@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz#554887821e009dd6d853f972fde6c5143f1de142" - integrity sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA== - -"@esbuild/android-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz#a7ce9d0721825fc578f9292a76d9e53334480ba2" - integrity sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A== - -"@esbuild/darwin-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz#2cb7659bd5d109803c593cfc414450d5430c8256" - integrity sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg== - -"@esbuild/darwin-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz#e741fa6b1abb0cd0364126ba34ca17fd5e7bf509" - integrity sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA== - -"@esbuild/freebsd-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz#2b64e7116865ca172d4ce034114c21f3c93e397c" - integrity sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g== - -"@esbuild/freebsd-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz#e5252551e66f499e4934efb611812f3820e990bb" - integrity sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA== - -"@esbuild/linux-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz#dc4acf235531cd6984f5d6c3b13dbfb7ddb303cb" - integrity sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw== - -"@esbuild/linux-arm@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz#56a900e39240d7d5d1d273bc053daa295c92e322" - integrity sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw== - -"@esbuild/linux-ia32@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz#d4a36d473360f6870efcd19d52bbfff59a2ed1cc" - integrity sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w== - -"@esbuild/linux-loong64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz#fcf0ab8c3eaaf45891d0195d4961cb18b579716a" - integrity sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg== - -"@esbuild/linux-mips64el@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz#598b67d34048bb7ee1901cb12e2a0a434c381c10" - integrity sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw== - -"@esbuild/linux-ppc64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz#3846c5df6b2016dab9bc95dde26c40f11e43b4c0" - integrity sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ== - -"@esbuild/linux-riscv64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz#173d4475b37c8d2c3e1707e068c174bb3f53d07d" - integrity sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA== - -"@esbuild/linux-s390x@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz#f7a4790105edcab8a5a31df26fbfac1aa3dacfab" - integrity sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w== - -"@esbuild/linux-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz#2ecc1284b1904aeb41e54c9ddc7fcd349b18f650" - integrity sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA== - -"@esbuild/netbsd-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz#e2863c2cd1501845995cb11adf26f7fe4be527b0" - integrity sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw== - -"@esbuild/netbsd-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz#93f7609e2885d1c0b5a1417885fba8d1fcc41272" - integrity sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA== - -"@esbuild/openbsd-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz#a1985604a203cdc325fd47542e106fafd698f02e" - integrity sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA== - -"@esbuild/openbsd-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz#8209e46c42f1ffbe6e4ef77a32e1f47d404ad42a" - integrity sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg== - -"@esbuild/openharmony-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz#8fade4441893d9cc44cbd7dcf3776f508ab6fb2f" - integrity sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag== - -"@esbuild/sunos-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz#980d4b9703a16f0f07016632424fc6d9a789dfc2" - integrity sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg== - -"@esbuild/win32-arm64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz#1c09a3633c949ead3d808ba37276883e71f6111a" - integrity sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg== - -"@esbuild/win32-ia32@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz#1b1e3a63ad4bef82200fef4e369e0fff7009eee5" - integrity sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ== - -"@esbuild/win32-x64@0.27.2": - version "0.27.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz#9e585ab6086bef994c6e8a5b3a0481219ada862b" - integrity sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ== - -"@img/colour@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@img/colour/-/colour-1.0.0.tgz#d2fabb223455a793bf3bf9c70de3d28526aa8311" - integrity sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw== - -"@img/sharp-darwin-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz#6e0732dcade126b6670af7aa17060b926835ea86" - integrity sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.2.4" - -"@img/sharp-darwin-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz#19bc1dd6eba6d5a96283498b9c9f401180ee9c7b" - integrity sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.2.4" - -"@img/sharp-libvips-darwin-arm64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz#2894c0cb87d42276c3889942e8e2db517a492c43" - integrity sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g== - -"@img/sharp-libvips-darwin-x64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz#e63681f4539a94af9cd17246ed8881734386f8cc" - integrity sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg== - -"@img/sharp-libvips-linux-arm64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz#b1b288b36864b3bce545ad91fa6dadcf1a4ad318" - integrity sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw== - -"@img/sharp-libvips-linux-arm@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz#b9260dd1ebe6f9e3bdbcbdcac9d2ac125f35852d" - integrity sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A== - -"@img/sharp-libvips-linux-ppc64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz#4b83ecf2a829057222b38848c7b022e7b4d07aa7" - integrity sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA== - -"@img/sharp-libvips-linux-riscv64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz#880b4678009e5a2080af192332b00b0aaf8a48de" - integrity sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA== - -"@img/sharp-libvips-linux-s390x@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz#74f343c8e10fad821b38f75ced30488939dc59ec" - integrity sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ== - -"@img/sharp-libvips-linux-x64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz#df4183e8bd8410f7d61b66859a35edeab0a531ce" - integrity sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw== - -"@img/sharp-libvips-linuxmusl-arm64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz#c8d6b48211df67137541007ee8d1b7b1f8ca8e06" - integrity sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw== - -"@img/sharp-libvips-linuxmusl-x64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz#be11c75bee5b080cbee31a153a8779448f919f75" - integrity sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg== - -"@img/sharp-linux-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz#7aa7764ef9c001f15e610546d42fce56911790cc" - integrity sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.2.4" - -"@img/sharp-linux-arm@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz#5fb0c3695dd12522d39c3ff7a6bc816461780a0d" - integrity sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.2.4" - -"@img/sharp-linux-ppc64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz#9c213a81520a20caf66978f3d4c07456ff2e0813" - integrity sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA== - optionalDependencies: - "@img/sharp-libvips-linux-ppc64" "1.2.4" - -"@img/sharp-linux-riscv64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz#cdd28182774eadbe04f62675a16aabbccb833f60" - integrity sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw== - optionalDependencies: - "@img/sharp-libvips-linux-riscv64" "1.2.4" - -"@img/sharp-linux-s390x@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz#93eac601b9f329bb27917e0e19098c722d630df7" - integrity sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg== - optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.2.4" - -"@img/sharp-linux-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz#55abc7cd754ffca5002b6c2b719abdfc846819a8" - integrity sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.2.4" - -"@img/sharp-linuxmusl-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz#d6515ee971bb62f73001a4829b9d865a11b77086" - integrity sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.2.4" - -"@img/sharp-linuxmusl-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz#d97978aec7c5212f999714f2f5b736457e12ee9f" - integrity sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.2.4" - -"@img/sharp-wasm32@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz#2f15803aa626f8c59dd7c9d0bbc766f1ab52cfa0" - integrity sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw== - dependencies: - "@emnapi/runtime" "^1.7.0" - -"@img/sharp-win32-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz#3706e9e3ac35fddfc1c87f94e849f1b75307ce0a" - integrity sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g== - -"@img/sharp-win32-ia32@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz#0b71166599b049e032f085fb9263e02f4e4788de" - integrity sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg== - -"@img/sharp-win32-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz#a81ffb00e69267cd0a1d626eaedb8a8430b2b2f8" - integrity sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw== - -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/remapping@^2.3.5": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" - integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pnpm/config.env-replace@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" - integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== - -"@pnpm/network.ca-file@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" - integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== - dependencies: - graceful-fs "4.2.10" - -"@pnpm/npm-conf@^2.1.0": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz#bb375a571a0bd63ab0a23bece33033c683e9b6b0" - integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw== - dependencies: - "@pnpm/config.env-replace" "^1.1.0" - "@pnpm/network.ca-file" "^1.0.1" - config-chain "^1.1.11" - -"@rolldown/pluginutils@1.0.0-beta.53": - version "1.0.0-beta.53" - resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz#c57a5234ae122671aff6fe72e673a7ed90f03f87" - integrity sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ== - -"@rollup/rollup-android-arm-eabi@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82" - integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== - -"@rollup/rollup-android-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf" - integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== - -"@rollup/rollup-darwin-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf" - integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== - -"@rollup/rollup-darwin-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246" - integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== - -"@rollup/rollup-freebsd-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22" - integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== - -"@rollup/rollup-freebsd-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803" - integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== - -"@rollup/rollup-linux-arm-gnueabihf@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a" - integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== - -"@rollup/rollup-linux-arm-musleabihf@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976" - integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== - -"@rollup/rollup-linux-arm64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45" - integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== - -"@rollup/rollup-linux-arm64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da" - integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== - -"@rollup/rollup-linux-loong64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7" - integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== - -"@rollup/rollup-linux-loong64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78" - integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== - -"@rollup/rollup-linux-ppc64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22" - integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== - -"@rollup/rollup-linux-ppc64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63" - integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== - -"@rollup/rollup-linux-riscv64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9" - integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== - -"@rollup/rollup-linux-riscv64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883" - integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== - -"@rollup/rollup-linux-s390x-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09" - integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== - -"@rollup/rollup-linux-x64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552" - integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== - -"@rollup/rollup-linux-x64-musl@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9" - integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== - -"@rollup/rollup-openbsd-x64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9" - integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== - -"@rollup/rollup-openharmony-arm64@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc" - integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== - -"@rollup/rollup-win32-arm64-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581" - integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== - -"@rollup/rollup-win32-ia32-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36" - integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== - -"@rollup/rollup-win32-x64-gnu@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab" - integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== - -"@rollup/rollup-win32-x64-msvc@4.59.0": - version "4.59.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" - integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== - -"@types/babel__core@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" - integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== - dependencies: - "@babel/types" "^7.28.2" - -"@types/estree@1.0.8", "@types/estree@^1.0.0": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== - -"@types/filesystem@*": - version "0.0.36" - resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" - integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA== - dependencies: - "@types/filewriter" "*" - -"@types/filewriter@*": - version "0.0.33" - resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8" - integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g== - -"@types/har-format@*": - version "1.2.16" - resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.16.tgz#b71ede8681400cc08b3685f061c31e416cf94944" - integrity sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A== - -"@types/minimatch@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" - integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== - -"@types/node@*": - version "25.0.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.3.tgz#79b9ac8318f373fbfaaf6e2784893efa9701f269" - integrity sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA== - dependencies: - undici-types "~7.16.0" - -"@types/react-dom@^19.2.3": - version "19.2.3" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c" - integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== - -"@types/react@^19.2.7": - version "19.2.7" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.7.tgz#84e62c0f23e8e4e5ac2cadcea1ffeacccae7f62f" - integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg== - dependencies: - csstype "^3.2.2" - -"@vitejs/plugin-react@^4.4.1 || ^5.0.0": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz#46f47be184c05a18839cb8705d79578b469ac6eb" - integrity sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ== - dependencies: - "@babel/core" "^7.28.5" - "@babel/plugin-transform-react-jsx-self" "^7.27.1" - "@babel/plugin-transform-react-jsx-source" "^7.27.1" - "@rolldown/pluginutils" "1.0.0-beta.53" - "@types/babel__core" "^7.20.5" - react-refresh "^0.18.0" - -"@webext-core/fake-browser@^1.3.2": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@webext-core/fake-browser/-/fake-browser-1.3.4.tgz#03c79865eeb96e44a92c9a1be5dccb00dd6d3011" - integrity sha512-nZcVWr3JpwpS5E6hKpbAwAMBM/AXZShnfW0F76udW8oLd6Kv0nbW6vFS07md4Na/0ntQonk3hFnlQYGYBAlTrA== - dependencies: - lodash.merge "^4.6.2" - -"@webext-core/isolated-element@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@webext-core/isolated-element/-/isolated-element-1.1.3.tgz#6f60bf045d31bb4d352669fdc9d0fcc95efca1ca" - integrity sha512-rbtnReIGdiVQb2UhK3MiECU6JqsiIo2K/luWvOdOw57Ot770Iw4KLCEPXUQMITIH5V5er2jfVK8hSWXaEOQGNQ== - dependencies: - is-potential-custom-element-name "^1.0.1" - -"@webext-core/match-patterns@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@webext-core/match-patterns/-/match-patterns-1.0.3.tgz#d590e5063e21a6a83d245700dfeb5489fbd99a77" - integrity sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A== - -"@wxt-dev/auto-icons@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@wxt-dev/auto-icons/-/auto-icons-1.1.0.tgz#17236284746aa6d0ddb908cad1cf6ce606add170" - integrity sha512-lDFZjDbrY5gDaapUuUOYTPudE88oB3Z7rTdg0N7iq2WIWga1h0bhzCJDaqNqMvPN2DCYvHFfA0cnqA12vEJjiA== - dependencies: - defu "^6.1.4" - fs-extra "^11.3.0" - sharp "^0.34.1" - -"@wxt-dev/browser@^0.1.32", "@wxt-dev/browser@^0.1.4": - version "0.1.32" - resolved "https://registry.yarnpkg.com/@wxt-dev/browser/-/browser-0.1.32.tgz#db47e3b88a405fbb9d1dd10b33d7ed6b466a9f84" - integrity sha512-jvfSppeLzlH4sOkIvMBJoA1pKoI+U5gTkjDwMKdkTWh0P/fj+KDyze3lzo3S6372viCm8tXUKNez+VKyVz2ZDw== - dependencies: - "@types/filesystem" "*" - "@types/har-format" "*" - -"@wxt-dev/module-react@^1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@wxt-dev/module-react/-/module-react-1.1.5.tgz#0edb824d299ecad8cacde2359f8172a6202b4469" - integrity sha512-KgsUrsgH5rBT8MwiipnDEOHBXmLvTIdFICrI7KjngqSf9DpVRn92HsKmToxY0AYpkP19hHWta2oNYFTzmmm++g== - dependencies: - "@vitejs/plugin-react" "^4.4.1 || ^5.0.0" - -"@wxt-dev/storage@^1.0.0": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@wxt-dev/storage/-/storage-1.2.6.tgz#1c1f49873ffb85654ba1c1c28585019b5d765225" - integrity sha512-f6AknnpJvhNHW4s0WqwSGCuZAj0fjP3EVNPBO5kB30pY+3Zt/nqZGqJN6FgBLCSkYjPJ8VL1hNX5LMVmvxQoDw== - dependencies: - "@wxt-dev/browser" "^0.1.4" - async-mutex "^0.5.0" - dequal "^2.0.3" - -acorn@^8.15.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -adm-zip@~0.5.x: - version "0.5.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" - integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== - -ansi-align@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-escapes@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.2.0.tgz#31b25afa3edd3efc09d98c2fee831d460ff06b49" - integrity sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw== - dependencies: - environment "^1.0.0" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" - integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== - -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.0.0, ansi-styles@^6.2.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" - integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -array-differ@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-4.0.0.tgz#aa3c891c653523290c880022f45b06a42051b026" - integrity sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw== - -array-union@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" - integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== - -async-mutex@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" - integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== - dependencies: - tslib "^2.4.0" - -async@^3.2.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -atomically@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/atomically/-/atomically-2.1.0.tgz#5a3ce8ea5ab57b65df589a3b63ef7b753cc0af07" - integrity sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q== - dependencies: - stubborn-fs "^2.0.0" - when-exit "^2.1.4" - -autoprefixer@^10.4.20: - version "10.4.23" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.23.tgz#c6aa6db8e7376fcd900f9fd79d143ceebad8c4e6" - integrity sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA== - dependencies: - browserslist "^4.28.1" - caniuse-lite "^1.0.30001760" - fraction.js "^5.3.4" - picocolors "^1.1.1" - postcss-value-parser "^4.2.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -baseline-browser-mapping@^2.9.0: - version "2.9.11" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz#53724708c8db5f97206517ecfe362dbe5181deea" - integrity sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -bluebird@~3.7: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -boxen@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-8.0.1.tgz#7e9fcbb45e11a2d7e6daa8fdcebfc3242fc19fe3" - integrity sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw== - dependencies: - ansi-align "^3.0.1" - camelcase "^8.0.0" - chalk "^5.3.0" - cli-boxes "^3.0.0" - string-width "^7.2.0" - type-fest "^4.21.0" - widest-line "^5.0.0" - wrap-ansi "^9.0.0" - -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.24.0, browserslist@^4.28.1: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== - dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bundle-name@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" - integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - dependencies: - run-applescript "^7.0.0" - -c12@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/c12/-/c12-3.3.3.tgz#cab6604e6e6117fc9e62439a8e8144bbbe5edcd6" - integrity sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q== - dependencies: - chokidar "^5.0.0" - confbox "^0.2.2" - defu "^6.1.4" - dotenv "^17.2.3" - exsolve "^1.0.8" - giget "^2.0.0" - jiti "^2.6.1" - ohash "^2.0.11" - pathe "^2.0.3" - perfect-debounce "^2.0.0" - pkg-types "^2.3.0" - rc9 "^2.1.2" - -cac@^6.7.14: - version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" - integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== - -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-8.0.0.tgz#c0d36d418753fb6ad9c5e0437579745c1c14a534" - integrity sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA== - -caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001760: - version "1.0.30001761" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz#4ca4c6e3792b24e8e2214baa568fc0e43de28191" - integrity sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g== - -chalk@^5.3.0: - version "5.6.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" - integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== - -chokidar@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - -chokidar@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-5.0.0.tgz#949c126a9238a80792be9a0265934f098af369a5" - integrity sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw== - dependencies: - readdirp "^5.0.0" - -chrome-launcher@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-1.2.0.tgz#bba61f558f450aef70bbda1f011c83c31c129302" - integrity sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q== - dependencies: - "@types/node" "*" - escape-string-regexp "^4.0.0" - is-wsl "^2.2.0" - lighthouse-logger "^2.0.1" - -ci-info@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa" - integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA== - -citty@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.6.tgz#0f7904da1ed4625e1a9ea7e0fa780981aab7c5e4" - integrity sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== - dependencies: - consola "^3.2.3" - -cli-boxes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" - integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== - -cli-cursor@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" - integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== - dependencies: - restore-cursor "^5.0.0" - -cli-spinners@^2.9.2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cli-truncate@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" - integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== - dependencies: - slice-ansi "^5.0.0" - string-width "^7.0.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.20: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A== - dependencies: - graceful-readlink ">= 1.0.0" - -commander@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@^9.1.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" - integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^1.4.7: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -confbox@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" - integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== - -confbox@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110" - integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== - -config-chain@^1.1.11: - version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - -configstore@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-7.1.0.tgz#45ba833d2d3ba0b8b7f0cff5c0544a45374af76b" - integrity sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg== - dependencies: - atomically "^2.0.3" - dot-prop "^9.0.0" - graceful-fs "^4.2.11" - xdg-basedir "^5.1.0" - -consola@^3.2.3, consola@^3.4.0, consola@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" - integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -css-select@^5.1.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.2.2.tgz#01b6e8d163637bb2dd6c982ca4ed65863682786e" - integrity sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== - dependencies: - boolbase "^1.0.0" - css-what "^6.1.0" - domhandler "^5.0.2" - domutils "^3.0.1" - nth-check "^2.0.1" - -css-what@^6.1.0: - version "6.2.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea" - integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssom@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" - integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== - -csstype@^3.2.2: - version "3.2.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" - integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== - -debounce@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" - integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== - -debug@^4.1.0, debug@^4.3.1, debug@^4.4.1: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -debug@~4.3.1: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -default-browser-id@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.1.tgz#f7a7ccb8f5104bf8e0f71ba3b1ccfa5eafdb21e8" - integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== - -default-browser@^5.2.1: - version "5.4.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.4.0.tgz#b55cf335bb0b465dd7c961a02cd24246aa434287" - integrity sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg== - dependencies: - bundle-name "^4.1.0" - default-browser-id "^5.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -defu@^6.1.4: - version "6.1.6" - resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.6.tgz#20970cc978d9be90ba6c792184a89c92db656e53" - integrity sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug== - -dequal@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - -destr@^2.0.3, destr@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.5.tgz#7d112ff1b925fb8d2079fac5bdb4a90973b51fdb" - integrity sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== - -detect-libc@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" - integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== - -didyoumean@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" - integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== - -dlv@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" - integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^3.0.1, domutils@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" - integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - -dot-prop@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-9.0.0.tgz#bae5982fe6dc6b8fddb92efef4f2ddff26779e92" - integrity sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ== - dependencies: - type-fest "^4.18.2" - -dotenv-expand@^12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-12.0.3.tgz#6323ceca51ca0c1b1f0055e2aba39c79781739a6" - integrity sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA== - dependencies: - dotenv "^16.4.5" - -dotenv@^16.4.5: - version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" - integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== - -dotenv@^17.2.3: - version "17.2.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" - integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== - -electron-to-chromium@^1.5.263: - version "1.5.267" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" - integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== - -emoji-regex@^10.3.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d" - integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -entities@^4.2.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -entities@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" - integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== - -environment@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" - integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== - -error-ex@^1.3.2: - version "1.3.4" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" - integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== - dependencies: - is-arrayish "^0.2.1" - -es-module-lexer@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== - -es6-error@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - -esbuild@^0.27.0, esbuild@^0.27.1: - version "0.27.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.2.tgz#d83ed2154d5813a5367376bb2292a9296fc83717" - integrity sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw== - optionalDependencies: - "@esbuild/aix-ppc64" "0.27.2" - "@esbuild/android-arm" "0.27.2" - "@esbuild/android-arm64" "0.27.2" - "@esbuild/android-x64" "0.27.2" - "@esbuild/darwin-arm64" "0.27.2" - "@esbuild/darwin-x64" "0.27.2" - "@esbuild/freebsd-arm64" "0.27.2" - "@esbuild/freebsd-x64" "0.27.2" - "@esbuild/linux-arm" "0.27.2" - "@esbuild/linux-arm64" "0.27.2" - "@esbuild/linux-ia32" "0.27.2" - "@esbuild/linux-loong64" "0.27.2" - "@esbuild/linux-mips64el" "0.27.2" - "@esbuild/linux-ppc64" "0.27.2" - "@esbuild/linux-riscv64" "0.27.2" - "@esbuild/linux-s390x" "0.27.2" - "@esbuild/linux-x64" "0.27.2" - "@esbuild/netbsd-arm64" "0.27.2" - "@esbuild/netbsd-x64" "0.27.2" - "@esbuild/openbsd-arm64" "0.27.2" - "@esbuild/openbsd-x64" "0.27.2" - "@esbuild/openharmony-arm64" "0.27.2" - "@esbuild/sunos-x64" "0.27.2" - "@esbuild/win32-arm64" "0.27.2" - "@esbuild/win32-ia32" "0.27.2" - "@esbuild/win32-x64" "0.27.2" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-goat@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-4.0.0.tgz#9424820331b510b0666b98f7873fe11ac4aa8081" - integrity sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escape-string-regexp@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" - integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== - -estree-walker@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" - integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== - dependencies: - "@types/estree" "^1.0.0" - -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - -exsolve@^1.0.7, exsolve@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.8.tgz#7f5e34da61cd1116deda5136e62292c096f50613" - integrity sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA== - -fast-glob@^3.3.2, fast-glob@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-redact@^3.1.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" - integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== - -fastq@^1.6.0: - version "1.20.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" - integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== - dependencies: - reusify "^1.0.4" - -fdir@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" - integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== - -filesize@^11.0.13: - version "11.0.13" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-11.0.13.tgz#8e61c5c4e8d7463c71d573c4f8051fb1ba4ff883" - integrity sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -firefox-profile@4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/firefox-profile/-/firefox-profile-4.7.0.tgz#97087b17a9a38fea58ec0acf2bca19a5cc121cb7" - integrity sha512-aGApEu5bfCNbA4PGUZiRJAIU6jKmghV2UVdklXAofnNtiDjqYw0czLS46W7IfFqVKgKhFB8Ao2YoNGHY4BoIMQ== - dependencies: - adm-zip "~0.5.x" - fs-extra "^11.2.0" - ini "^4.1.3" - minimist "^1.2.8" - xml2js "^0.6.2" - -form-data-encoder@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-4.1.0.tgz#497cedc94810bd5d53b99b5d4f6c152d5cbc9db2" - integrity sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw== - -formdata-node@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-6.0.3.tgz#48f8e2206ae2befded82af621ef015f08168dc6d" - integrity sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg== - -fraction.js@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" - integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== - -fs-extra@^11.2.0, fs-extra@^11.3.0, fs-extra@^11.3.2: - version "11.3.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.3.tgz#a27da23b72524e81ac6c3815cc0179b8c74c59ee" - integrity sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -fx-runner@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/fx-runner/-/fx-runner-1.4.0.tgz#7a3f0374cc78c6c689ef75937b7b0cd75428c509" - integrity sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg== - dependencies: - commander "2.9.0" - shell-quote "1.7.3" - spawn-sync "1.0.15" - when "3.7.7" - which "1.2.4" - winreg "0.0.12" - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-east-asian-width@^1.0.0, get-east-asian-width@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz#9bc4caa131702b4b61729cb7e42735bc550c9ee6" - integrity sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q== - -get-port-please@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.2.0.tgz#0ce3cee194c448ac640ec39dc357a500f5d7d2bb" - integrity sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A== - -"giget@^1.2.3 || ^2.0.0", giget@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/giget/-/giget-2.0.0.tgz#395fc934a43f9a7a29a29d55b99f23e30c14f195" - integrity sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA== - dependencies: - citty "^0.1.6" - consola "^3.4.0" - defu "^6.1.4" - node-fetch-native "^1.6.6" - nypm "^0.6.0" - pathe "^2.0.3" - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -global-directory@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e" - integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== - dependencies: - ini "4.1.1" - -graceful-fs@4.2.10: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== - -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -hookable@^5.5.3: - version "5.5.3" - resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d" - integrity sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ== - -html-escaper@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-3.0.3.tgz#4d336674652beb1dcbc29ef6b6ba7f6be6fdfed6" - integrity sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ== - -htmlparser2@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.0.0.tgz#77ad249037b66bf8cc99c6e286ef73b83aeb621d" - integrity sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.3" - domutils "^3.2.1" - entities "^6.0.0" - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - -import-meta-resolve@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz#08cb85b5bd37ecc8eb1e0f670dc2767002d43734" - integrity sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg== - -inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" - integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== - -ini@^1.3.4, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -ini@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.3.tgz#4c359675a6071a46985eb39b14e4a2c0ec98a795" - integrity sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg== - -is-absolute@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.1.7.tgz#847491119fccb5fb436217cc737f7faad50f603f" - integrity sha512-Xi9/ZSn4NFapG8RP98iNPMOeaV3mXPisxKxzKtHVqr3g56j/fBn+yZmnxSVAA8lmZbl2J9b/a4kJvfU3hqQYgA== - dependencies: - is-relative "^0.1.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-fullwidth-code-point@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" - integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== - -is-fullwidth-code-point@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz#046b2a6d4f6b156b2233d3207d4b5a9783999b98" - integrity sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== - dependencies: - get-east-asian-width "^1.3.1" - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-in-ci@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-in-ci/-/is-in-ci-1.0.0.tgz#9a86bbda7e42c6129902e0574c54b018fbb6ab88" - integrity sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg== - -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-installed-globally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a" - integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== - dependencies: - global-directory "^4.0.1" - is-path-inside "^4.0.0" - -is-interactive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" - integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== - -is-npm@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.1.0.tgz#f70e0b6c132dfc817ac97d3badc0134945b098d3" - integrity sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db" - integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - -is-primitive@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" - integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== - -is-relative@^0.1.0: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.1.3.tgz#905fee8ae86f45b3ec614bc3c15c869df0876e82" - integrity sha512-wBOr+rNM4gkAZqoLRJI4myw5WzzIdQosFAAbnvfXP5z1LyzgAI3ivOKehC5KfqlQJZoihVhirgtCBj378Eg8GA== - -is-unicode-supported@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" - integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== - -is-unicode-supported@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" - integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== - dependencies: - is-inside-container "^1.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" - integrity sha512-d2eJzK691yZwPHcv1LbeAOa91yMJ9QmfTgSO1oXB65ezVhXQsxBac2vEB4bMVms9cGzaA99n6V2viHMq82VLDw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jiti@^1.21.7: - version "1.21.7" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" - integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== - -jiti@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" - integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-tokens@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" - integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-parse-even-better-errors@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz#b43d35e89c0f3be6b5fbbe9dc6c82467b30c28da" - integrity sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jszip@^3.10.1, jszip@^3.2.2: - version "3.10.1" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" - integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - setimmediate "^1.0.5" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -ky@^1.2.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/ky/-/ky-1.14.2.tgz#385d6d05d2825502e68898ace125124e6fe7357d" - integrity sha512-q3RBbsO5A5zrPhB6CaCS8ZUv+NWCXv6JJT4Em0i264G9W0fdPB8YRfnnEi7Dm7X7omAkBIPojzYJ2D1oHTHqug== - -latest-version@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-9.0.0.tgz#e91ed216e7a4badc6f73b66c65adb46c58ec6ba1" - integrity sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA== - dependencies: - package-json "^10.0.0" - -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - -lighthouse-logger@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz#c0b39daee22035ce28551f3503c5935d0b5e1bf3" - integrity sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg== - dependencies: - debug "^4.4.1" - marky "^1.2.2" - -lilconfig@^3.1.1, lilconfig@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" - integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -lines-and-columns@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.4.tgz#d00318855905d2660d8c0822e3f5a4715855fc42" - integrity sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A== - -linkedom@^0.18.12: - version "0.18.12" - resolved "https://registry.yarnpkg.com/linkedom/-/linkedom-0.18.12.tgz#a8b1a1942b567dcb1888093df311055da1349a14" - integrity sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q== - dependencies: - css-select "^5.1.0" - cssom "^0.5.0" - html-escaper "^3.0.3" - htmlparser2 "^10.0.0" - uhyphen "^0.2.0" - -listr2@^8.3.3: - version "8.3.3" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.3.3.tgz#815fc8f738260ff220981bf9e866b3e11e8121bf" - integrity sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ== - dependencies: - cli-truncate "^4.0.0" - colorette "^2.0.20" - eventemitter3 "^5.0.1" - log-update "^6.1.0" - rfdc "^1.4.1" - wrap-ansi "^9.0.0" - -local-pkg@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.2.tgz#c03d208787126445303f8161619dc701afa4abb5" - integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A== - dependencies: - mlly "^1.7.4" - pkg-types "^2.3.0" - quansync "^0.2.11" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -log-symbols@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-6.0.0.tgz#bb95e5f05322651cac30c0feb6404f9f2a8a9439" - integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== - dependencies: - chalk "^5.3.0" - is-unicode-supported "^1.3.0" - -log-update@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" - integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== - dependencies: - ansi-escapes "^7.0.0" - cli-cursor "^5.0.0" - slice-ansi "^7.1.0" - strip-ansi "^7.1.0" - wrap-ansi "^9.0.0" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -magic-string@^0.30.21: - version "0.30.21" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" - integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.5" - -magicast@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" - integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== - dependencies: - "@babel/parser" "^7.25.4" - "@babel/types" "^7.25.4" - source-map-js "^1.2.0" - -make-error@^1.3.2: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -many-keys-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/many-keys-map/-/many-keys-map-2.0.1.tgz#3ae9f97cff78e08f79311d4e9c42fb3cc583f61e" - integrity sha512-DHnZAD4phTbZ+qnJdjoNEVU1NecYoSdbOOoVmTDH46AuxDkEVh3MxTVpXq10GtcTC6mndN9dkv1rNfpjRcLnOw== - -marky@^1.2.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/marky/-/marky-1.3.0.tgz#422b63b0baf65022f02eda61a238eccdbbc14997" - integrity sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mimic-function@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" - integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== - -minimatch@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== - dependencies: - "@isaacs/brace-expansion" "^5.0.0" - -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mlly@^1.7.4, mlly@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e" - integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== - dependencies: - acorn "^8.15.0" - pathe "^2.0.3" - pkg-types "^1.3.1" - ufo "^1.6.1" - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multimatch@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-6.0.0.tgz#c72a9bddbc94baa4727efd613b5d22a1fe4d6ee3" - integrity sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ== - dependencies: - "@types/minimatch" "^3.0.5" - array-differ "^4.0.0" - array-union "^3.0.1" - minimatch "^3.0.4" - -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -nano-spawn@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.3.tgz#ef8d89a275eebc8657e67b95fc312a6527a05b8d" - integrity sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA== - -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - -node-fetch-native@^1.6.6, node-fetch-native@^1.6.7: - version "1.6.7" - resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz#9d09ca63066cc48423211ed4caf5d70075d76a71" - integrity sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== - -node-forge@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.4.0.tgz#1c7b7d8bdc2d078739f58287d589d903a11b2fc2" - integrity sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ== - -node-notifier@10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-10.0.1.tgz#0e82014a15a8456c4cfcdb25858750399ae5f1c7" - integrity sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.5" - shellwords "^0.1.1" - uuid "^8.3.2" - which "^2.0.2" - -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -nypm@^0.6.0, nypm@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.6.2.tgz#467512024948398fafa73cea30a3ed9efc5af071" - integrity sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g== - dependencies: - citty "^0.1.6" - consola "^3.4.2" - pathe "^2.0.3" - pkg-types "^2.3.0" - tinyexec "^1.0.1" - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - -obug@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/obug/-/obug-2.1.1.tgz#2cba74ff241beb77d63055ddf4cd1e9f90b538be" - integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ== - -ofetch@^1.4.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.5.1.tgz#5c43cc56e03398b273014957060344254505c5c7" - integrity sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA== - dependencies: - destr "^2.0.5" - node-fetch-native "^1.6.7" - ufo "^1.6.1" - -ohash@^2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.11.tgz#60b11e8cff62ca9dee88d13747a5baa145f5900b" - integrity sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== - -on-exit-leak-free@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" - integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== - -onetime@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" - integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== - dependencies: - mimic-function "^5.0.0" - -open@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/open/-/open-10.2.0.tgz#b9d855be007620e80b6fb05fac98141fe62db73c" - integrity sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA== - dependencies: - default-browser "^5.2.1" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - wsl-utils "^0.1.0" - -open@^8.4.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -ora@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-8.2.0.tgz#8fbbb7151afe33b540dd153f171ffa8bd38e9861" - integrity sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw== - dependencies: - chalk "^5.3.0" - cli-cursor "^5.0.0" - cli-spinners "^2.9.2" - is-interactive "^2.0.0" - is-unicode-supported "^2.0.0" - log-symbols "^6.0.0" - stdin-discarder "^0.2.2" - string-width "^7.2.0" - strip-ansi "^7.1.0" - -os-shim@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" - integrity sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A== - -package-json@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-10.0.1.tgz#e49ee07b8de63b638e7f1b5bb353733e428fe7d7" - integrity sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg== - dependencies: - ky "^1.2.0" - registry-auth-token "^5.0.2" - registry-url "^6.0.1" - semver "^7.6.0" - -pako@~1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parse-json@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-7.1.1.tgz#68f7e6f0edf88c54ab14c00eb700b753b14e2120" - integrity sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw== - dependencies: - "@babel/code-frame" "^7.21.4" - error-ex "^1.3.2" - json-parse-even-better-errors "^3.0.0" - lines-and-columns "^2.0.3" - type-fest "^3.8.0" - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -pathe@^2.0.1, pathe@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" - integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== - -perfect-debounce@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/perfect-debounce/-/perfect-debounce-2.0.0.tgz#0ff94f1ecbe0a6bca4b1703a2ed08bbe43739aa7" - integrity sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pino-abstract-transport@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz#de241578406ac7b8a33ce0d77ae6e8a0b3b68a60" - integrity sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw== - dependencies: - split2 "^4.0.0" - -pino-std-serializers@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" - integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== - -pino@9.7.0: - version "9.7.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-9.7.0.tgz#ff7cd86eb3103ee620204dbd5ca6ffda8b53f645" - integrity sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^2.0.0" - pino-std-serializers "^7.0.0" - process-warning "^5.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^4.0.1" - thread-stream "^3.0.0" - -pirates@^4.0.1: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - -pkg-types@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" - integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== - dependencies: - confbox "^0.1.8" - mlly "^1.7.4" - pathe "^2.0.1" - -pkg-types@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.3.0.tgz#037f2c19bd5402966ff6810e32706558cb5b5726" - integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== - dependencies: - confbox "^0.2.2" - exsolve "^1.0.7" - pathe "^2.0.3" - -postcss-import@^15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" - integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-js@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.1.0.tgz#003b63c6edde948766e40f3daf7e997ae43a5ce6" - integrity sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw== - dependencies: - camelcase-css "^2.0.1" - -"postcss-load-config@^4.0.2 || ^5.0 || ^6.0": - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" - integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== - dependencies: - lilconfig "^3.1.1" - -postcss-nested@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" - integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== - dependencies: - postcss-selector-parser "^6.1.1" - -postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" - integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.4.47, postcss@^8.5.10, postcss@^8.5.6: - version "8.5.10" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.10.tgz#8992d8c30acf3f12169e7c09514a12fed7e48356" - integrity sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ== - dependencies: - nanoid "^3.3.11" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process-warning@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7" - integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== - -promise-toolbox@0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/promise-toolbox/-/promise-toolbox-0.21.0.tgz#6919876f9dea375f01b4ddaec4206db83cb0aa55" - integrity sha512-NV8aTmpwrZv+Iys54sSFOBx3tuVaOBvvrft5PNppnxy9xpU/akHbaWIril22AB22zaPgrgwKdD0KsrM0ptUtpg== - dependencies: - make-error "^1.3.2" - -prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== - -"publish-browser-extension@^2.3.0 || ^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/publish-browser-extension/-/publish-browser-extension-3.0.3.tgz#ae8de586d18d493dd629ea1537d2d8ab9567e165" - integrity sha512-cBINZCkLo7YQaGoUvEHthZ0sDzgJQht28IS+SFMT2omSNhGsPiVNRkWir3qLiTrhGhW9Ci2KVHpA1QAMoBdL2g== - dependencies: - cac "^6.7.14" - consola "^3.4.2" - dotenv "^17.2.3" - form-data-encoder "^4.1.0" - formdata-node "^6.0.3" - listr2 "^8.3.3" - ofetch "^1.4.1" - zod "^3.25.76 || ^4.0.0" - -pupa@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-3.3.0.tgz#bc4036f9e8920c08ad472bc18fb600067cb83810" - integrity sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA== - dependencies: - escape-goat "^4.0.0" - -quansync@^0.2.11: - version "0.2.11" - resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.11.tgz#f9c3adda2e1272e4f8cf3f1457b04cbdb4ee692a" - integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -rc9@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/rc9/-/rc9-2.1.2.tgz#6282ff638a50caa0a91a31d76af4a0b9cbd1080d" - integrity sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg== - dependencies: - defu "^6.1.4" - destr "^2.0.3" - -rc@1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-dom@^19.2.3: - version "19.2.3" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.3.tgz#f0b61d7e5c4a86773889fcc1853af3ed5f215b17" - integrity sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg== - dependencies: - scheduler "^0.27.0" - -react-refresh@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062" - integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw== - -react@^19.2.3: - version "19.2.3" - resolved "https://registry.yarnpkg.com/react/-/react-19.2.3.tgz#d83e5e8e7a258cf6b4fe28640515f99b87cd19b8" - integrity sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA== - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== - dependencies: - pify "^2.3.0" - -readable-stream@^2.2.2, readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== - -readdirp@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-5.0.0.tgz#fbf1f71a727891d685bb1786f9ba74084f6e2f91" - integrity sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ== - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -real-require@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" - integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== - -registry-auth-token@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.1.0.tgz#3c659047ecd4caebd25bc1570a3aa979ae490eca" - integrity sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw== - dependencies: - "@pnpm/npm-conf" "^2.1.0" - -registry-url@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58" - integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q== - dependencies: - rc "1.2.8" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve@^1.1.7, resolve@^1.22.8: - version "1.22.11" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" - integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== - dependencies: - is-core-module "^2.16.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" - integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== - dependencies: - onetime "^7.0.0" - signal-exit "^4.1.0" - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -rfdc@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" - integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== - -rollup@^4.43.0: - version "4.59.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f" - integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== - dependencies: - "@types/estree" "1.0.8" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.59.0" - "@rollup/rollup-android-arm64" "4.59.0" - "@rollup/rollup-darwin-arm64" "4.59.0" - "@rollup/rollup-darwin-x64" "4.59.0" - "@rollup/rollup-freebsd-arm64" "4.59.0" - "@rollup/rollup-freebsd-x64" "4.59.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.59.0" - "@rollup/rollup-linux-arm-musleabihf" "4.59.0" - "@rollup/rollup-linux-arm64-gnu" "4.59.0" - "@rollup/rollup-linux-arm64-musl" "4.59.0" - "@rollup/rollup-linux-loong64-gnu" "4.59.0" - "@rollup/rollup-linux-loong64-musl" "4.59.0" - "@rollup/rollup-linux-ppc64-gnu" "4.59.0" - "@rollup/rollup-linux-ppc64-musl" "4.59.0" - "@rollup/rollup-linux-riscv64-gnu" "4.59.0" - "@rollup/rollup-linux-riscv64-musl" "4.59.0" - "@rollup/rollup-linux-s390x-gnu" "4.59.0" - "@rollup/rollup-linux-x64-gnu" "4.59.0" - "@rollup/rollup-linux-x64-musl" "4.59.0" - "@rollup/rollup-openbsd-x64" "4.59.0" - "@rollup/rollup-openharmony-arm64" "4.59.0" - "@rollup/rollup-win32-arm64-msvc" "4.59.0" - "@rollup/rollup-win32-ia32-msvc" "4.59.0" - "@rollup/rollup-win32-x64-gnu" "4.59.0" - "@rollup/rollup-win32-x64-msvc" "4.59.0" - fsevents "~2.3.2" - -run-applescript@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911" - integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-stable-stringify@^2.3.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" - integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== - -sax@>=0.6.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.3.tgz#fcebae3b756cdc8428321805f4b70f16ec0ab5db" - integrity sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ== - -scheduler@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" - integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== - -scule@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3" - integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g== - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.5, semver@^7.6.0, semver@^7.6.3, semver@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== - -set-value@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" - integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== - dependencies: - is-plain-object "^2.0.4" - is-primitive "^3.0.1" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -sharp@^0.34.1: - version "0.34.5" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.5.tgz#b6f148e4b8c61f1797bde11a9d1cfebbae2c57b0" - integrity sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg== - dependencies: - "@img/colour" "^1.0.0" - detect-libc "^2.1.2" - semver "^7.7.3" - optionalDependencies: - "@img/sharp-darwin-arm64" "0.34.5" - "@img/sharp-darwin-x64" "0.34.5" - "@img/sharp-libvips-darwin-arm64" "1.2.4" - "@img/sharp-libvips-darwin-x64" "1.2.4" - "@img/sharp-libvips-linux-arm" "1.2.4" - "@img/sharp-libvips-linux-arm64" "1.2.4" - "@img/sharp-libvips-linux-ppc64" "1.2.4" - "@img/sharp-libvips-linux-riscv64" "1.2.4" - "@img/sharp-libvips-linux-s390x" "1.2.4" - "@img/sharp-libvips-linux-x64" "1.2.4" - "@img/sharp-libvips-linuxmusl-arm64" "1.2.4" - "@img/sharp-libvips-linuxmusl-x64" "1.2.4" - "@img/sharp-linux-arm" "0.34.5" - "@img/sharp-linux-arm64" "0.34.5" - "@img/sharp-linux-ppc64" "0.34.5" - "@img/sharp-linux-riscv64" "0.34.5" - "@img/sharp-linux-s390x" "0.34.5" - "@img/sharp-linux-x64" "0.34.5" - "@img/sharp-linuxmusl-arm64" "0.34.5" - "@img/sharp-linuxmusl-x64" "0.34.5" - "@img/sharp-wasm32" "0.34.5" - "@img/sharp-win32-arm64" "0.34.5" - "@img/sharp-win32-ia32" "0.34.5" - "@img/sharp-win32-x64" "0.34.5" - -shell-quote@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== - -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - -signal-exit@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slice-ansi@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" - integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== - dependencies: - ansi-styles "^6.0.0" - is-fullwidth-code-point "^4.0.0" - -slice-ansi@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.2.tgz#adf7be70aa6d72162d907cd0e6d5c11f507b5403" - integrity sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w== - dependencies: - ansi-styles "^6.2.1" - is-fullwidth-code-point "^5.0.0" - -sonic-boom@^4.0.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.0.tgz#e59a525f831210fa4ef1896428338641ac1c124d" - integrity sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww== - dependencies: - atomic-sleep "^1.0.0" - -source-map-js@^1.2.0, source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-support@0.5.21: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.4: - version "0.7.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" - integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== - -spawn-sync@1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" - integrity sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw== - dependencies: - concat-stream "^1.4.7" - os-shim "^0.1.2" - -split2@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" - integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== - -split@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -stdin-discarder@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" - integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^7.0.0, string-width@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" - integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== - dependencies: - emoji-regex "^10.3.0" - get-east-asian-width "^1.0.0" - strip-ansi "^7.1.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" - integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== - dependencies: - ansi-regex "^6.0.1" - -strip-bom@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-5.0.0.tgz#88d2e135d154dca7a5e06b4a4ba9653b6bdc0dd2" - integrity sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A== - -strip-json-comments@5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.2.tgz#14a76abd63b84a6d2419d14f26a0281d0cf6ea46" - integrity sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -strip-literal@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.1.0.tgz#222b243dd2d49c0bcd0de8906adbd84177196032" - integrity sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg== - dependencies: - js-tokens "^9.0.1" - -stubborn-fs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/stubborn-fs/-/stubborn-fs-2.0.0.tgz#628750f81c51c44c04ef50fc70ed4d1caea4f1e9" - integrity sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA== - dependencies: - stubborn-utils "^1.0.1" - -stubborn-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/stubborn-utils/-/stubborn-utils-1.0.2.tgz#0d9c58ab550f40936235056c7ea6febd925c4d41" - integrity sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg== - -sucrase@^3.35.0: - version "3.35.1" - resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1" - integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.2" - commander "^4.0.0" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - tinyglobby "^0.2.11" - ts-interface-checker "^0.1.9" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tailwindcss@^3.4.17: - version "3.4.19" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.19.tgz#af2a0a4ae302d52ebe078b6775e799e132500ee2" - integrity sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ== - dependencies: - "@alloc/quick-lru" "^5.2.0" - arg "^5.0.2" - chokidar "^3.6.0" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.3.2" - glob-parent "^6.0.2" - is-glob "^4.0.3" - jiti "^1.21.7" - lilconfig "^3.1.3" - micromatch "^4.0.8" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.1.1" - postcss "^8.4.47" - postcss-import "^15.1.0" - postcss-js "^4.0.1" - postcss-load-config "^4.0.2 || ^5.0 || ^6.0" - postcss-nested "^6.2.0" - postcss-selector-parser "^6.1.2" - resolve "^1.22.8" - sucrase "^3.35.0" - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -thread-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" - integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== - dependencies: - real-require "^0.2.0" - -through@2: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -tinyexec@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251" - integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== - -tinyglobby@^0.2.11, tinyglobby@^0.2.15: - version "0.2.15" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" - integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== - dependencies: - fdir "^6.5.0" - picomatch "^4.0.3" - -tmp@0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" - integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - -tslib@^2.4.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -type-fest@^3.8.0: - version "3.13.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" - integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== - -type-fest@^4.18.2, type-fest@^4.21.0: - version "4.41.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" - integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -typescript@^5.9.3: - version "5.9.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" - integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== - -ufo@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" - integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== - -uhyphen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/uhyphen/-/uhyphen-0.2.0.tgz#8fdf0623314486e020a3c00ee5cc7a12fe722b81" - integrity sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA== - -undici-types@~7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" - integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== - -"unimport@^3.13.1 || ^4.0.0 || ^5.0.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/unimport/-/unimport-5.6.0.tgz#22cd39a0eb74b76c5e64ed6bec27ca4fd4b086e3" - integrity sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A== - dependencies: - acorn "^8.15.0" - escape-string-regexp "^5.0.0" - estree-walker "^3.0.3" - local-pkg "^1.1.2" - magic-string "^0.30.21" - mlly "^1.8.0" - pathe "^2.0.3" - picomatch "^4.0.3" - pkg-types "^2.3.0" - scule "^1.3.0" - strip-literal "^3.1.0" - tinyglobby "^0.2.15" - unplugin "^2.3.11" - unplugin-utils "^0.3.1" - -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -unplugin-utils@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz#ef2873670a6a2a21bd2c9d31307257cc863a709c" - integrity sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog== - dependencies: - pathe "^2.0.3" - picomatch "^4.0.3" - -unplugin@^2.3.11: - version "2.3.11" - resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.11.tgz#411e020dd2ba90e2fbe1e7bd63a5a399e6ee3b54" - integrity sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww== - dependencies: - "@jridgewell/remapping" "^2.3.5" - acorn "^8.15.0" - picomatch "^4.0.3" - webpack-virtual-modules "^0.6.2" - -update-browserslist-db@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -update-notifier@7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-7.3.1.tgz#49af1ad6acfa0ea01c0d0f3c04047c154ead7096" - integrity sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA== - dependencies: - boxen "^8.0.1" - chalk "^5.3.0" - configstore "^7.0.0" - is-in-ci "^1.0.0" - is-installed-globally "^1.0.0" - is-npm "^6.0.0" - latest-version "^9.0.0" - pupa "^3.1.0" - semver "^7.6.3" - xdg-basedir "^5.1.0" - -util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -"vite-node@^3.2.4 || ^5.0.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-5.2.0.tgz#26d2f684ffbd1a049d8ea45cd481f72bd21f5f23" - integrity sha512-7UT39YxUukIA97zWPXUGb0SGSiLexEGlavMwU3HDE6+d/HJhKLjLqu4eX2qv6SQiocdhKLRcusroDwXHQ6CnRQ== - dependencies: - cac "^6.7.14" - es-module-lexer "^1.7.0" - obug "^2.0.0" - pathe "^2.0.3" - vite "^7.2.2" - -"vite@^5.4.19 || ^6.3.4 || ^7.0.0", vite@^7.2.2: - version "7.3.5" - resolved "https://registry.yarnpkg.com/vite/-/vite-7.3.5.tgz#90c2d0b7b94a224e7e7dcf22d2912ff0b5291165" - integrity sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww== - dependencies: - esbuild "^0.27.0" - fdir "^6.5.0" - picomatch "^4.0.3" - postcss "^8.5.6" - rollup "^4.43.0" - tinyglobby "^0.2.15" - optionalDependencies: - fsevents "~2.3.3" - -watchpack@2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" - integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -web-ext-run@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/web-ext-run/-/web-ext-run-0.2.4.tgz#eb732ac303c184a62382e77126fe72bb67386e21" - integrity sha512-rQicL7OwuqWdQWI33JkSXKcp7cuv1mJG8u3jRQwx/8aDsmhbTHs9ZRmNYOL+LX0wX8edIEQX8jj4bB60GoXtKA== - dependencies: - "@babel/runtime" "7.28.2" - "@devicefarmer/adbkit" "3.3.8" - chrome-launcher "1.2.0" - debounce "1.2.1" - es6-error "4.1.1" - firefox-profile "4.7.0" - fx-runner "1.4.0" - multimatch "6.0.0" - node-notifier "10.0.1" - parse-json "7.1.1" - pino "9.7.0" - promise-toolbox "0.21.0" - set-value "4.1.0" - source-map-support "0.5.21" - strip-bom "5.0.0" - strip-json-comments "5.0.2" - tmp "0.2.5" - update-notifier "7.3.1" - watchpack "2.4.4" - zip-dir "2.0.0" - -webpack-virtual-modules@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" - integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== - -when-exit@^2.1.4: - version "2.1.5" - resolved "https://registry.yarnpkg.com/when-exit/-/when-exit-2.1.5.tgz#53fa4ffa2ba4c792213fb6617eb7d08f0dcb1a9f" - integrity sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg== - -when@3.7.7: - version "3.7.7" - resolved "https://registry.yarnpkg.com/when/-/when-3.7.7.tgz#aba03fc3bb736d6c88b091d013d8a8e590d84718" - integrity sha512-9lFZp/KHoqH6bPKjbWqa+3Dg/K/r2v0X/3/G2x4DBGchVS2QX2VXL3cZV994WQVnTM1/PD71Az25nAzryEUugw== - -which@1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.4.tgz#1557f96080604e5b11b3599eb9f45b50a9efd722" - integrity sha512-zDRAqDSBudazdfM9zpiI30Fu9ve47htYXcGi3ln0wfKu2a7SmrT6F3VDoYONu//48V8Vz4TdCRNPjtvyRO3yBA== - dependencies: - is-absolute "^0.1.7" - isexe "^1.1.1" - -which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-5.0.0.tgz#b74826a1e480783345f0cd9061b49753c9da70d0" - integrity sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA== - dependencies: - string-width "^7.0.0" - -winreg@0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/winreg/-/winreg-0.0.12.tgz#07105554ba1a9d08979251d129475bffae3006b7" - integrity sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^9.0.0: - version "9.0.2" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98" - integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== - dependencies: - ansi-styles "^6.2.1" - string-width "^7.0.0" - strip-ansi "^7.1.0" - -wsl-utils@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/wsl-utils/-/wsl-utils-0.1.0.tgz#8783d4df671d4d50365be2ee4c71917a0557baab" - integrity sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw== - dependencies: - is-wsl "^3.1.0" - -wxt@^0.20.6: - version "0.20.13" - resolved "https://registry.yarnpkg.com/wxt/-/wxt-0.20.13.tgz#147d2aafefa8d2f6c5846a151b642d4da0c2f9ea" - integrity sha512-FwQEk+0a4/pYha6rTKGl5iicU6kRYDBDiElJf55CFEfoJKqvGzBTZpphafurQfqU1X0hvAm9w5GEWC0thXI6wQ== - dependencies: - "@1natsu/wait-element" "^4.1.2" - "@aklinker1/rollup-plugin-visualizer" "5.12.0" - "@webext-core/fake-browser" "^1.3.2" - "@webext-core/isolated-element" "^1.1.2" - "@webext-core/match-patterns" "^1.0.3" - "@wxt-dev/browser" "^0.1.32" - "@wxt-dev/storage" "^1.0.0" - async-mutex "^0.5.0" - c12 "^3.3.2" - cac "^6.7.14" - chokidar "^4.0.3" - ci-info "^4.3.1" - consola "^3.4.2" - defu "^6.1.4" - dotenv "^17.2.3" - dotenv-expand "^12.0.3" - esbuild "^0.27.1" - fast-glob "^3.3.3" - filesize "^11.0.13" - fs-extra "^11.3.2" - get-port-please "^3.2.0" - giget "^1.2.3 || ^2.0.0" - hookable "^5.5.3" - import-meta-resolve "^4.2.0" - is-wsl "^3.1.0" - json5 "^2.2.3" - jszip "^3.10.1" - linkedom "^0.18.12" - magicast "^0.3.5" - minimatch "^10.1.1" - nano-spawn "^1.0.3" - normalize-path "^3.0.0" - nypm "^0.6.2" - ohash "^2.0.11" - open "^10.2.0" - ora "^8.2.0" - perfect-debounce "^2.0.0" - picocolors "^1.1.1" - prompts "^2.4.2" - publish-browser-extension "^2.3.0 || ^3.0.2" - scule "^1.3.0" - unimport "^3.13.1 || ^4.0.0 || ^5.0.0" - vite "^5.4.19 || ^6.3.4 || ^7.0.0" - vite-node "^3.2.4 || ^5.0.0" - web-ext-run "^0.2.4" - -xdg-basedir@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9" - integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ== - -xml2js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.5.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -zip-dir@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/zip-dir/-/zip-dir-2.0.0.tgz#c5df6e15c8f9efeb4e320377028c9f5c8277c932" - integrity sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg== - dependencies: - async "^3.2.0" - jszip "^3.2.2" - -"zod@^3.25.76 || ^4.0.0": - version "4.2.1" - resolved "https://registry.yarnpkg.com/zod/-/zod-4.2.1.tgz#07f0388c7edbfd5f5a2466181cb4adf5b5dbd57b" - integrity sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw== +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 9 + cacheKey: 10c0 + +"@1natsu/wait-element@npm:^4.1.2": + version: 4.1.2 + resolution: "@1natsu/wait-element@npm:4.1.2" + dependencies: + defu: "npm:^6.1.4" + many-keys-map: "npm:^2.0.1" + checksum: 10c0/41ac5b0243306b725023a47d25df11032ece58b778dff05508b38629d447af090df095ad5be6c45f025ec6feaef05f802984698eb9ed312a647fe86b99d34bc3 + languageName: node + linkType: hard + +"@adobe/css-tools@npm:^4.4.0": + version: 4.5.0 + resolution: "@adobe/css-tools@npm:4.5.0" + checksum: 10c0/fc969e1117098eb4cccdb73beb2508daa0e52760af1183d6288bafea59204943490ab3ede28593032ffb8929c0cee270b2a53254fe61139ab00604ea8fc33cea + languageName: node + linkType: hard + +"@aklinker1/rollup-plugin-visualizer@npm:5.12.0": + version: 5.12.0 + resolution: "@aklinker1/rollup-plugin-visualizer@npm:5.12.0" + dependencies: + open: "npm:^8.4.0" + picomatch: "npm:^2.3.1" + source-map: "npm:^0.7.4" + yargs: "npm:^17.5.1" + peerDependencies: + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rollup: + optional: true + bin: + rollup-plugin-visualizer: dist/bin/cli.js + checksum: 10c0/9081f9cb6d6b2efb94ed5dfc888bcaafc3582e05c6e7263f99870163ad73e3ac0b937ff8e95224301a740364d5aef6021d9da3710851bad39ab587c7108a65c3 + languageName: node + linkType: hard + +"@alloc/quick-lru@npm:^5.2.0": + version: 5.2.0 + resolution: "@alloc/quick-lru@npm:5.2.0" + checksum: 10c0/7b878c48b9d25277d0e1a9b8b2f2312a314af806b4129dc902f2bc29ab09b58236e53964689feec187b28c80d2203aff03829754773a707a8a5987f1b7682d92 + languageName: node + linkType: hard + +"@asamuzakjp/css-color@npm:^5.1.11": + version: 5.1.11 + resolution: "@asamuzakjp/css-color@npm:5.1.11" + dependencies: + "@asamuzakjp/generational-cache": "npm:^1.0.1" + "@csstools/css-calc": "npm:^3.2.0" + "@csstools/css-color-parser": "npm:^4.1.0" + "@csstools/css-parser-algorithms": "npm:^4.0.0" + "@csstools/css-tokenizer": "npm:^4.0.0" + checksum: 10c0/32720bdff8daea6a8847aba6cdfae55baa3b4a2690b51d21db7f0382bbd183f3d9f2d5126df50afd889062635684b2819e47113629ee2e80c99389e75f48d060 + languageName: node + linkType: hard + +"@asamuzakjp/dom-selector@npm:^7.1.1": + version: 7.1.1 + resolution: "@asamuzakjp/dom-selector@npm:7.1.1" + dependencies: + "@asamuzakjp/generational-cache": "npm:^1.0.1" + "@asamuzakjp/nwsapi": "npm:^2.3.9" + bidi-js: "npm:^1.0.3" + css-tree: "npm:^3.2.1" + is-potential-custom-element-name: "npm:^1.0.1" + checksum: 10c0/8cec1c618781c94de5836a215bbe5aafb4d8b835b18c51faf8547f4574afa39f92def3951e40123860062467613dd825f1e1600ff32e8045cc099a91796dcfb8 + languageName: node + linkType: hard + +"@asamuzakjp/generational-cache@npm:^1.0.1": + version: 1.0.1 + resolution: "@asamuzakjp/generational-cache@npm:1.0.1" + checksum: 10c0/1de62de43764e13fca3b9a31b7ea9b1bf0780fe053d266e40378a19ff8c66b543e011e6a0df02d410cd59bf981126706f176cdbb938985165202c4a079fe1057 + languageName: node + linkType: hard + +"@asamuzakjp/nwsapi@npm:^2.3.9": + version: 2.3.9 + resolution: "@asamuzakjp/nwsapi@npm:2.3.9" + checksum: 10c0/869b81382e775499c96c45c6dbe0d0766a6da04bcf0abb79f5333535c4e19946851acaa43398f896e2ecc5a1de9cf3db7cf8c4b1afac1ee3d15e21584546d74d + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.10.4": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.27.2": + version: 7.28.5 + resolution: "@babel/compat-data@npm:7.28.5" + checksum: 10c0/702a25de73087b0eba325c1d10979eed7c9b6662677386ba7b5aa6eace0fc0676f78343bae080a0176ae26f58bd5535d73b9d0fbb547fef377692e8b249353a7 + languageName: node + linkType: hard + +"@babel/core@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helpers": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" + dependencies: + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" + dependencies: + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/helper-module-transforms@npm:7.28.3" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/helpers@npm:7.28.4" + dependencies: + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.4" + checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" + dependencies: + "@babel/types": "npm:^7.28.5" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + languageName: node + linkType: hard + +"@babel/parser@npm:^7.29.3": + version: 7.29.7 + resolution: "@babel/parser@npm:7.29.7" + dependencies: + "@babel/types": "npm:^7.29.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/65133038f80b54a714d6027cb77cee3f9a6b5c4c6842ce674301e13947cbcbfa8055e63acaf1b84c085d34226a14425b2c2b97b829e0e226d2e8f1299942a51d + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-self@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 + languageName: node + linkType: hard + +"@babel/runtime@npm:7.28.2": + version: 7.28.2 + resolution: "@babel/runtime@npm:7.28.2" + checksum: 10c0/c20afe253629d53a405a610b12a62ac74d341a2c1e0fb202bbef0c118f6b5c84f94bf16039f58fd0483dd256901259930a43976845bdeb180cab1f882c21b6e0 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.12.5": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e + languageName: node + linkType: hard + +"@babel/template@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.5" + debug: "npm:^4.3.1" + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + languageName: node + linkType: hard + +"@babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/types@npm:7.29.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/b6623994c69717fa27294f5fa46d59140338e2d86c6c1c13085c84ef7d53086ee357fbf4fe9abe3dd3da75734dc77c4c0df2f90fb29e667558bb3b3fb705e88f + languageName: node + linkType: hard + +"@bramus/specificity@npm:^2.4.2": + version: 2.4.2 + resolution: "@bramus/specificity@npm:2.4.2" + dependencies: + css-tree: "npm:^3.0.0" + bin: + specificity: bin/cli.js + checksum: 10c0/c5f4e04e0bca0d2202598207a5eb0733c8109d12a68a329caa26373bec598d99db5bb785b8865fefa00fc01b08c6068138807ceb11a948fe15e904ed6cf4ba72 + languageName: node + linkType: hard + +"@csstools/color-helpers@npm:^6.1.0": + version: 6.1.0 + resolution: "@csstools/color-helpers@npm:6.1.0" + checksum: 10c0/ebb71eebcd6dde16a89d687c30d4c7f315f9079ec803497ebac0528d0a9ef09847eaf167b4565c4919f156e32e7ca2167199ac2d2020f184f7888551a3b4712b + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^3.2.0, @csstools/css-calc@npm:^3.2.1": + version: 3.2.1 + resolution: "@csstools/css-calc@npm:3.2.1" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/0191c8d1cd4dffa0d3b6bfd1e78a721934b1d7a6c972966e4fdaa72208c6789e8ff443ee81764a32f1e6107825695b5524ef2b4dc1681b5b29230f2a1277e5df + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^4.1.0": + version: 4.1.9 + resolution: "@csstools/css-color-parser@npm:4.1.9" + dependencies: + "@csstools/color-helpers": "npm:^6.1.0" + "@csstools/css-calc": "npm:^3.2.1" + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.0 + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/d9330a1d5b207230e97a522c9b73c1b625dbfbca7a91ac8888e05917efb01c6aa4075153e5af6ce028c3848660d3f24cf7b0f0760a080361089e1a3841e586c7 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-parser-algorithms@npm:4.0.0" + peerDependencies: + "@csstools/css-tokenizer": ^4.0.0 + checksum: 10c0/94558c2428d6ef0ddef542e86e0a8376aa1263a12a59770abb13ba50d7b83086822c75433f32aa2e7fef00555e1cc88292f9ca5bce79aed232bb3fed73b1528d + languageName: node + linkType: hard + +"@csstools/css-syntax-patches-for-csstree@npm:^1.1.3": + version: 1.1.6 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.6" + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + checksum: 10c0/4f1322833df87dfb19ec5b23cba5475ceeff8391940dd0eeaf9b28df362f23c354c31f704c4bdef9bd749ae5f0825ded022df3fea1fab39af9b6aab42370ba53 + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/css-tokenizer@npm:4.0.0" + checksum: 10c0/669cf3d0f9c8e1ffdf8c9955ad8beba0c8cfe03197fe29a4fcbd9ee6f7a18856cfa42c62670021a75183d9ab37f5d14a866e6a9df753a6c07f59e36797a9ea9f + languageName: node + linkType: hard + +"@devicefarmer/adbkit-logcat@npm:^2.1.2": + version: 2.1.3 + resolution: "@devicefarmer/adbkit-logcat@npm:2.1.3" + checksum: 10c0/d8d6108a0c47f994fd3073f19c8de9e38c6c70b420c55be3fc1a924b873f35cb24120f11e0173ab94c2f14e190f575ff62dc7de801b3272d56f6e46c4be8cde1 + languageName: node + linkType: hard + +"@devicefarmer/adbkit-monkey@npm:~1.2.1": + version: 1.2.1 + resolution: "@devicefarmer/adbkit-monkey@npm:1.2.1" + checksum: 10c0/3c397e7b5242034e29455b94792b6b3ce7d0adbd3e9da59b85c24aa6a5e99ae45f36078f56a8dc5b8df2e1c8f57726f88e5017081c6a4301e1945cf88d8864a2 + languageName: node + linkType: hard + +"@devicefarmer/adbkit@npm:3.3.8": + version: 3.3.8 + resolution: "@devicefarmer/adbkit@npm:3.3.8" + dependencies: + "@devicefarmer/adbkit-logcat": "npm:^2.1.2" + "@devicefarmer/adbkit-monkey": "npm:~1.2.1" + bluebird: "npm:~3.7" + commander: "npm:^9.1.0" + debug: "npm:~4.3.1" + node-forge: "npm:^1.3.1" + split: "npm:~1.0.1" + bin: + adbkit: bin/adbkit + checksum: 10c0/1d15f598ef7e2eae76580d463b61005a966faa5a5bf14e555657166ca638c381d88e5ead074ad8bbb3dc71c0f4ddd85d88cf13f6a1d014b66330292626ad7066 + languageName: node + linkType: hard + +"@emnapi/core@npm:1.11.1": + version: 1.11.1 + resolution: "@emnapi/core@npm:1.11.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.2" + tslib: "npm:^2.4.0" + checksum: 10c0/2c6defdac2d1d26090384655d7d6c9614fa553853b1760597686749e9375dc2aa0dae80a2615b81c254600f5d531d07d8466cde0d331a8caae64b93f3ca5937e + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.11.1": + version: 1.11.2 + resolution: "@emnapi/core@npm:1.11.2" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.2" + tslib: "npm:^2.4.0" + checksum: 10c0/424ca1607f498e524eb58db1095e6cc2082986edfd84a74b116d4e2c6e43b5e9d557ea7f0d7b9adf7f6d5023e25ac2ed6bd08caf0043d3d8602598d79579c2cd + languageName: node + linkType: hard + +"@emnapi/runtime@npm:1.11.1": + version: 1.11.1 + resolution: "@emnapi/runtime@npm:1.11.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/04332fb62076afc440aa23316c04bec42f584ca8b074e5507d08e2b33a47cbe0493b1aadb8f3c1057b64ae1e17f5bde1a7bc37f7facc9d0bc25c18197cbd366f + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.11.1": + version: 1.11.2 + resolution: "@emnapi/runtime@npm:1.11.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/d8d500059fe9c0864571c79ce29439c1b6fb44d7ec4ac865a2aaaa7469194e5d91ebdc820cabd37c3d373478b725a8b60771733e4743cfef41fc86d9a1f2eab0 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.7.0": + version: 1.7.1 + resolution: "@emnapi/runtime@npm:1.7.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.2.2, @emnapi/wasi-threads@npm:^1.2.2": + version: 1.2.2 + resolution: "@emnapi/wasi-threads@npm:1.2.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f0dc8269d6b20ae5a7c7b36e7a6a333452009d461038ef4febb29da2f3f78c1e2b1576d7e8970a5c5789ed3caedc1f80f5b0c2a5373bdaf8d03b20432bb55747 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@exodus/bytes@npm:^1.11.0, @exodus/bytes@npm:^1.15.0, @exodus/bytes@npm:^1.6.0": + version: 1.15.1 + resolution: "@exodus/bytes@npm:1.15.1" + peerDependencies: + "@noble/hashes": ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + "@noble/hashes": + optional: true + checksum: 10c0/333056a6953bbf875d9f3b86c32314de29458d842e5f56f6ef8034b18c2d9660184550093d1bae5de0064043d5e23f54cc03148798d9d29cf5167ac03f2e9f8c + languageName: node + linkType: hard + +"@gar/promise-retry@npm:^1.0.0": + version: 1.0.3 + resolution: "@gar/promise-retry@npm:1.0.3" + checksum: 10c0/885b02c8b0d75b2d215da25f3b639158c4fbe8fefe0d79163304534b9a6d0710db4b7699f7cd3cc1a730792bff04cbe19f4850a62d3e105a663eaeec88f38332 + languageName: node + linkType: hard + +"@img/colour@npm:^1.0.0": + version: 1.0.0 + resolution: "@img/colour@npm:1.0.0" + checksum: 10c0/02261719c1e0d7aa5a2d585981954f2ac126f0c432400aa1a01b925aa2c41417b7695da8544ee04fd29eba7ecea8eaf9b8bef06f19dc8faba78f94eeac40667d + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" + dependencies: + "@emnapi/runtime": "npm:^1.7.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.1.4, @napi-rs/wasm-runtime@npm:^1.1.6": + version: 1.1.6 + resolution: "@napi-rs/wasm-runtime@npm:1.1.6" + dependencies: + "@tybys/wasm-util": "npm:^0.10.3" + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/344518bf3ef65051dda4c00969f293aa4a21ab7dc7822b3f48519b17cd5eaa3f0bc34898d115d50ba59b1817a0cb905d46f7a7223c8249239cd14c28db388e10 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@npmcli/redact@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/redact@npm:4.0.0" + checksum: 10c0/a1e9ba9c70a6b40e175bda2c3dd8cfdaf096e6b7f7a132c855c083c8dfe545c3237cd56702e2e6627a580b1d63373599d49a1192c4078a85bf47bbde824df31c + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.137.0": + version: 0.137.0 + resolution: "@oxc-project/types@npm:0.137.0" + checksum: 10c0/5a6a50174e5ac79aebf38a120fe57be7a84c8bb0c77117f30de15183aa5ab0161e78364d2d3725397090e362e5c5f6eda754b53057b0b63983e3ee604f888aca + languageName: node + linkType: hard + +"@pnpm/config.env-replace@npm:^1.1.0": + version: 1.1.0 + resolution: "@pnpm/config.env-replace@npm:1.1.0" + checksum: 10c0/4cfc4a5c49ab3d0c6a1f196cfd4146374768b0243d441c7de8fa7bd28eaab6290f514b98490472cc65dbd080d34369447b3e9302585e1d5c099befd7c8b5e55f + languageName: node + linkType: hard + +"@pnpm/network.ca-file@npm:^1.0.1": + version: 1.0.2 + resolution: "@pnpm/network.ca-file@npm:1.0.2" + dependencies: + graceful-fs: "npm:4.2.10" + checksum: 10c0/95f6e0e38d047aca3283550719155ce7304ac00d98911e4ab026daedaf640a63bd83e3d13e17c623fa41ac72f3801382ba21260bcce431c14fbbc06430ecb776 + languageName: node + linkType: hard + +"@pnpm/npm-conf@npm:^2.1.0": + version: 2.3.1 + resolution: "@pnpm/npm-conf@npm:2.3.1" + dependencies: + "@pnpm/config.env-replace": "npm:^1.1.0" + "@pnpm/network.ca-file": "npm:^1.0.1" + config-chain: "npm:^1.1.11" + checksum: 10c0/778a3a34ff7d6000a2594d2a9821f873f737bc56367865718b2cf0ba5d366e49689efe7975148316d7afd8e6f1dcef7d736fbb6ea7ef55caadd1dc93a36bb302 + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-android-arm64@npm:1.1.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-darwin-arm64@npm:1.1.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-darwin-x64@npm:1.1.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-freebsd-x64@npm:1.1.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.1.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.1.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.1.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.1.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.1.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.1.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.1.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.1.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.1.3" + dependencies: + "@emnapi/core": "npm:1.11.1" + "@emnapi/runtime": "npm:1.11.1" + "@napi-rs/wasm-runtime": "npm:^1.1.6" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.1.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.1.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-beta.53": + version: 1.0.0-beta.53 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.53" + checksum: 10c0/e8b0a7eb76be22f6f103171f28072de821525a4e400454850516da91a7381957932ff0ce495f227bcb168e86815788b0c1d249ca9e34dca366a82c8825b714ce + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:^1.0.0, @rolldown/pluginutils@npm:^1.0.1": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@tailwindcss/node@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/node@npm:4.3.2" + dependencies: + "@jridgewell/remapping": "npm:^2.3.5" + enhanced-resolve: "npm:5.21.6" + jiti: "npm:^2.7.0" + lightningcss: "npm:1.32.0" + magic-string: "npm:^0.30.21" + source-map-js: "npm:^1.2.1" + tailwindcss: "npm:4.3.2" + checksum: 10c0/3b83caeb3a913b1a537640bbe4df3ac68dcfba1c95b5c2dedc3788bf6437b6ebb06512ec31ae1fb3aaf570eee0a2672e35ef872969a441e07861c2d248a9da3e + languageName: node + linkType: hard + +"@tailwindcss/oxide-android-arm64@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-android-arm64@npm:4.3.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-darwin-arm64@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-darwin-arm64@npm:4.3.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-darwin-x64@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-darwin-x64@npm:4.3.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-freebsd-x64@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-freebsd-x64@npm:4.3.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.3.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-arm64-gnu@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-linux-arm64-gnu@npm:4.3.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-arm64-musl@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-linux-arm64-musl@npm:4.3.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-x64-gnu@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-linux-x64-gnu@npm:4.3.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-x64-musl@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-linux-x64-musl@npm:4.3.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@tailwindcss/oxide-wasm32-wasi@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-wasm32-wasi@npm:4.3.2" + dependencies: + "@emnapi/core": "npm:^1.11.1" + "@emnapi/runtime": "npm:^1.11.1" + "@emnapi/wasi-threads": "npm:^1.2.2" + "@napi-rs/wasm-runtime": "npm:^1.1.4" + "@tybys/wasm-util": "npm:^0.10.2" + tslib: "npm:^2.8.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@tailwindcss/oxide-win32-arm64-msvc@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-win32-arm64-msvc@npm:4.3.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-win32-x64-msvc@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide-win32-x64-msvc@npm:4.3.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@tailwindcss/oxide@npm:4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/oxide@npm:4.3.2" + dependencies: + "@tailwindcss/oxide-android-arm64": "npm:4.3.2" + "@tailwindcss/oxide-darwin-arm64": "npm:4.3.2" + "@tailwindcss/oxide-darwin-x64": "npm:4.3.2" + "@tailwindcss/oxide-freebsd-x64": "npm:4.3.2" + "@tailwindcss/oxide-linux-arm-gnueabihf": "npm:4.3.2" + "@tailwindcss/oxide-linux-arm64-gnu": "npm:4.3.2" + "@tailwindcss/oxide-linux-arm64-musl": "npm:4.3.2" + "@tailwindcss/oxide-linux-x64-gnu": "npm:4.3.2" + "@tailwindcss/oxide-linux-x64-musl": "npm:4.3.2" + "@tailwindcss/oxide-wasm32-wasi": "npm:4.3.2" + "@tailwindcss/oxide-win32-arm64-msvc": "npm:4.3.2" + "@tailwindcss/oxide-win32-x64-msvc": "npm:4.3.2" + dependenciesMeta: + "@tailwindcss/oxide-android-arm64": + optional: true + "@tailwindcss/oxide-darwin-arm64": + optional: true + "@tailwindcss/oxide-darwin-x64": + optional: true + "@tailwindcss/oxide-freebsd-x64": + optional: true + "@tailwindcss/oxide-linux-arm-gnueabihf": + optional: true + "@tailwindcss/oxide-linux-arm64-gnu": + optional: true + "@tailwindcss/oxide-linux-arm64-musl": + optional: true + "@tailwindcss/oxide-linux-x64-gnu": + optional: true + "@tailwindcss/oxide-linux-x64-musl": + optional: true + "@tailwindcss/oxide-wasm32-wasi": + optional: true + "@tailwindcss/oxide-win32-arm64-msvc": + optional: true + "@tailwindcss/oxide-win32-x64-msvc": + optional: true + checksum: 10c0/9c82a1eb1e6cd7a29118a4f9cfae5fbf83950757cfbb5e51fb212b1e17c9195632ce245f873aee6ad3133921dbb617d050d7f12530905ab1f2683d41909b1de4 + languageName: node + linkType: hard + +"@tailwindcss/postcss@npm:^4.3.2": + version: 4.3.2 + resolution: "@tailwindcss/postcss@npm:4.3.2" + dependencies: + "@alloc/quick-lru": "npm:^5.2.0" + "@tailwindcss/node": "npm:4.3.2" + "@tailwindcss/oxide": "npm:4.3.2" + postcss: "npm:^8.5.15" + tailwindcss: "npm:4.3.2" + checksum: 10c0/577998b219b5e209b1fdc33812f6024ff18b4fd43a0e5be9bba02c02ea593e10bcc776a6f5671780782f9ac29732b88cb185d0b944be009e19c1724c8d8b1970 + languageName: node + linkType: hard + +"@tanstack/react-virtual@npm:^3.14.5": + version: 3.14.5 + resolution: "@tanstack/react-virtual@npm:3.14.5" + dependencies: + "@tanstack/virtual-core": "npm:3.17.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/f3dd2da5e5e0a94756e47f11fa76bfe0485e845da72b8022a4a593e2906aa60b827a5f0d7b6063092c76874fed8ddc69bcbf759298733ced24a66a82e8d58f21 + languageName: node + linkType: hard + +"@tanstack/virtual-core@npm:3.17.3": + version: 3.17.3 + resolution: "@tanstack/virtual-core@npm:3.17.3" + checksum: 10c0/e6199a0c25d1780e62f57c9f72432342e2cabd5b1197cf66b0f2250c9c0bbecbd0c53e7bb639804c4c7931f9cfa57efa80f6a64fefe6614bf14871f682c17aa3 + languageName: node + linkType: hard + +"@testing-library/dom@npm:^10.4.1": + version: 10.4.1 + resolution: "@testing-library/dom@npm:10.4.1" + dependencies: + "@babel/code-frame": "npm:^7.10.4" + "@babel/runtime": "npm:^7.12.5" + "@types/aria-query": "npm:^5.0.1" + aria-query: "npm:5.3.0" + dom-accessibility-api: "npm:^0.5.9" + lz-string: "npm:^1.5.0" + picocolors: "npm:1.1.1" + pretty-format: "npm:^27.0.2" + checksum: 10c0/19ce048012d395ad0468b0dbcc4d0911f6f9e39464d7a8464a587b29707eed5482000dad728f5acc4ed314d2f4d54f34982999a114d2404f36d048278db815b1 + languageName: node + linkType: hard + +"@testing-library/jest-dom@npm:^6.9.1": + version: 6.9.1 + resolution: "@testing-library/jest-dom@npm:6.9.1" + dependencies: + "@adobe/css-tools": "npm:^4.4.0" + aria-query: "npm:^5.0.0" + css.escape: "npm:^1.5.1" + dom-accessibility-api: "npm:^0.6.3" + picocolors: "npm:^1.1.1" + redent: "npm:^3.0.0" + checksum: 10c0/4291ebd2f0f38d14cefac142c56c337941775a5807e2a3d6f1a14c2fbd6be76a18e498ed189e95bedc97d9e8cf1738049bc76c85b5bc5e23fae7c9e10f7b3a12 + languageName: node + linkType: hard + +"@testing-library/react@npm:^16.3.2": + version: 16.3.2 + resolution: "@testing-library/react@npm:16.3.2" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/f9c7f0915e1b5f7b750e6c7d8b51f091b8ae7ea99bacb761d7b8505ba25de9cfcb749a0f779f1650fb268b499dd79165dc7e1ee0b8b4cb63430d3ddc81ffe044 + languageName: node + linkType: hard + +"@testing-library/user-event@npm:^14.6.1": + version: 14.6.1 + resolution: "@testing-library/user-event@npm:14.6.1" + peerDependencies: + "@testing-library/dom": ">=7.21.4" + checksum: 10c0/75fea130a52bf320d35d46ed54f3eec77e71a56911b8b69a3fe29497b0b9947b2dc80d30f04054ad4ce7f577856ae3e5397ea7dff0ef14944d3909784c7a93fe + languageName: node + linkType: hard + +"@tybys/wasm-util@npm:^0.10.2, @tybys/wasm-util@npm:^0.10.3": + version: 0.10.3 + resolution: "@tybys/wasm-util@npm:0.10.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/fd2bd2a79c6cd8c79ed1cf7a0fa375c64589264c88a27acaf9756d556b453ea222b62a4f68dd2fbb8b3a78b6bab3b1f4fb2431b6afc6aeda8344b53a521a1cd3 + languageName: node + linkType: hard + +"@types/aria-query@npm:^5.0.1": + version: 5.0.4 + resolution: "@types/aria-query@npm:5.0.4" + checksum: 10c0/dc667bc6a3acc7bba2bccf8c23d56cb1f2f4defaa704cfef595437107efaa972d3b3db9ec1d66bc2711bfc35086821edd32c302bffab36f2e79b97f312069f08 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.20.5": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*": + version: 7.28.0 + resolution: "@types/babel__traverse@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.2" + checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 + languageName: node + linkType: hard + +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/estree@npm:^1.0.0": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/filesystem@npm:*": + version: 0.0.36 + resolution: "@types/filesystem@npm:0.0.36" + dependencies: + "@types/filewriter": "npm:*" + checksum: 10c0/3ebec32f0494b0a2612187d148e9f253ff55672c53f566d9a1e6d891eb6e2372df93c252b594b2775bc53e6660c4c37fdb05dc1b26e72b60a31010da8e1f7317 + languageName: node + linkType: hard + +"@types/filewriter@npm:*": + version: 0.0.33 + resolution: "@types/filewriter@npm:0.0.33" + checksum: 10c0/363ef9a658a961ceae04f52934562e4ebdcdc3a2564dd8544f593d77113c16574938b6ba4fea0bee418c37bda0668c1e03dfedb6adf00d55853f51fb3a59247b + languageName: node + linkType: hard + +"@types/har-format@npm:*": + version: 1.2.16 + resolution: "@types/har-format@npm:1.2.16" + checksum: 10c0/77e952bc219db0c1f0588cab3b95865bc343b922e8423a76fbbd6a757c40709a256933fa415eb8fefda6ea5897c8e3dd3191bb8a82b37c13d9232467d31ae485 + languageName: node + linkType: hard + +"@types/minimatch@npm:^3.0.5": + version: 3.0.5 + resolution: "@types/minimatch@npm:3.0.5" + checksum: 10c0/a1a19ba342d6f39b569510f621ae4bbe972dc9378d15e9a5e47904c440ee60744f5b09225bc73be1c6490e3a9c938eee69eb53debf55ce1f15761201aa965f97 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 25.0.3 + resolution: "@types/node@npm:25.0.3" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/b7568f0d765d9469621615e2bb257c7fd1953d95e9acbdb58dffb6627a2c4150d405a4600aa1ad8a40182a94fe5f903cafd3c0a2f5132814debd0e3bfd61f835 + languageName: node + linkType: hard + +"@types/qrcode@npm:^1.5.6": + version: 1.5.6 + resolution: "@types/qrcode@npm:1.5.6" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/84844ca63e5f32bc47d44dda0f8a6f7cdcc7ce44e7b24f10f19d50796f31d12c058f702a8f7d352c9e82a023a9abc36fa1ad01ddf0a209dd8ed4562ea76481fc + languageName: node + linkType: hard + +"@types/react-dom@npm:^19.2.3": + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 + languageName: node + linkType: hard + +"@types/react@npm:^19.2.7": + version: 19.2.7 + resolution: "@types/react@npm:19.2.7" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/a7b75f1f9fcb34badd6f84098be5e35a0aeca614bc91f93d2698664c0b2ba5ad128422bd470ada598238cebe4f9e604a752aead7dc6f5a92261d0c7f9b27cfd1 + languageName: node + linkType: hard + +"@types/webextension-polyfill@npm:>=0.10.5": + version: 0.12.5 + resolution: "@types/webextension-polyfill@npm:0.12.5" + checksum: 10c0/019727139514bc39d25d9d94be8a24ca8a57f49954c3e546a54cc0df213b3d356391bf45917c43084370da462b4c903bbaa26a33845458ca868b8726961113e1 + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:^4.4.1 || ^5.0.0": + version: 5.1.2 + resolution: "@vitejs/plugin-react@npm:5.1.2" + dependencies: + "@babel/core": "npm:^7.28.5" + "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" + "@rolldown/pluginutils": "npm:1.0.0-beta.53" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.18.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/d788f269cdf7474425071ba7c4ea7013f174ddaef12b758defe809a551a03ac62a4a80cd858872deb618e7936ccc7cffe178bc12b62e9c836a467e13f15b9390 + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:^6.0.3": + version: 6.0.3 + resolution: "@vitejs/plugin-react@npm:6.0.3" + dependencies: + "@rolldown/pluginutils": "npm:^1.0.1" + peerDependencies: + "@rolldown/plugin-babel": ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + "@rolldown/plugin-babel": + optional: true + babel-plugin-react-compiler: + optional: true + checksum: 10c0/592a93178c0eec420759d529a2c964a7103860808f549f72993b96a38c287c929a0d371cacbd932c37ee2118b830348db752cc01a31d688c71d19d74567251fa + languageName: node + linkType: hard + +"@vitest/expect@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/expect@npm:4.1.9" + dependencies: + "@standard-schema/spec": "npm:^1.1.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.1.9" + "@vitest/utils": "npm:4.1.9" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/243bacaed2cba5e0ea4ec7465662fcec465a358a0e06381e337fac49426aa67a73b104fbb9d65d8bccadfba8f70e27f57ffb897aacfa140f579a556367357875 + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/mocker@npm:4.1.9" + dependencies: + "@vitest/spy": "npm:4.1.9" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/707353b7435bbfd441cc754e4ee7bc5921b70d07b051c6e414b6bbe4ca369154702b0ddeb603389469fe87ca1983e002eb2d55044582661f54a1945dd27e5c82 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/pretty-format@npm:4.1.9" + dependencies: + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/5b96295f25ab885616230ad1355fc82f490bebb39cc707688d7c8969c08270d7e076ed8a10af4e762ed57145193c6061a1f549f136f0ded344f8db0c2b3fb3de + languageName: node + linkType: hard + +"@vitest/runner@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/runner@npm:4.1.9" + dependencies: + "@vitest/utils": "npm:4.1.9" + pathe: "npm:^2.0.3" + checksum: 10c0/d206b4891a64b1f55c346f832b0a7b489108094d8ae34438d3b53e78be7b45b139fa95ffa027c98c357bd532268ee573168de1943235b7eed32a9236ed5978bb + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/snapshot@npm:4.1.9" + dependencies: + "@vitest/pretty-format": "npm:4.1.9" + "@vitest/utils": "npm:4.1.9" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/c3099df12ad1f9c1e180441856c9eb82f1990f87ff16aafedd6fa19978eaff20bc59220b692a99fcc822daef86eab256ba3dadb49544b7bd625b57c49cd9d995 + languageName: node + linkType: hard + +"@vitest/spy@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/spy@npm:4.1.9" + checksum: 10c0/e51f328f55b76e8ba66e5e18f183484a8dc0a092685b101112d3e9fb8e989ddca162c98ddf00254476502c25bc05c4ec1e277fd6ad8bfc702464c08f6b5dd115 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/utils@npm:4.1.9" + dependencies: + "@vitest/pretty-format": "npm:4.1.9" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/d55506c077fd72c091eb66f02926f0abf72801c87a085f565698289562f47befa114ae2c680ab8736dfe46abab0cfd6b8031f2ac519bafeb37578aa6e5ad03c5 + languageName: node + linkType: hard + +"@webext-core/fake-browser@npm:^1.3.4": + version: 1.5.2 + resolution: "@webext-core/fake-browser@npm:1.5.2" + dependencies: + "@types/webextension-polyfill": "npm:>=0.10.5" + lodash.merge: "npm:^4.6.2" + checksum: 10c0/5f9dbb9fbb8f4d01932d35dd5f2a38410d68608bf593f96c7587b4420eef7a52982754f312fdd5e020b315c2c51146b4d795c2b7bafb0c4e5c578123a147d148 + languageName: node + linkType: hard + +"@webext-core/isolated-element@npm:^1.1.3": + version: 1.1.5 + resolution: "@webext-core/isolated-element@npm:1.1.5" + dependencies: + is-potential-custom-element-name: "npm:^1.0.1" + checksum: 10c0/348a32904f6a640cdb02b0d0d5f1569cc8afd7c943e75bb4bfedbbf7695ff42b3177eb2a3da5efd636072887b5dcd9969cf0bedb42bdc386763bf198996207d7 + languageName: node + linkType: hard + +"@webext-core/match-patterns@npm:^1.0.3": + version: 1.0.3 + resolution: "@webext-core/match-patterns@npm:1.0.3" + checksum: 10c0/eb219006f03420ef2221d17b07b28923078ca817cf2f4f5cf031856137c102b0c0f75ce1c18627514e767fcc672cf6a673c2bbb3a0a20c322ff3c1d6b6bb9920 + languageName: node + linkType: hard + +"@wxt-dev/auto-icons@npm:^1.1.0": + version: 1.1.0 + resolution: "@wxt-dev/auto-icons@npm:1.1.0" + dependencies: + defu: "npm:^6.1.4" + fs-extra: "npm:^11.3.0" + sharp: "npm:^0.34.1" + peerDependencies: + wxt: ">=0.19.0" + checksum: 10c0/a8369463aec3c30c887493977197a2ae495453028d714dcc96c42f516adebb7124256799a47dca47b79457465d50e0585659b0bf3fabb2479b0f90880176a123 + languageName: node + linkType: hard + +"@wxt-dev/browser@npm:^0.1.4": + version: 0.1.32 + resolution: "@wxt-dev/browser@npm:0.1.32" + dependencies: + "@types/filesystem": "npm:*" + "@types/har-format": "npm:*" + checksum: 10c0/2b7fd414c7d89f595a2959d554036b25c849a4a4a049a7b3fdbc9498f26c36445317bbb8bfa89dbf2d38ecc3e012911e4a51dccfdc102c21cbef2b1d23374c3c + languageName: node + linkType: hard + +"@wxt-dev/browser@npm:^0.2.0": + version: 0.2.0 + resolution: "@wxt-dev/browser@npm:0.2.0" + dependencies: + "@types/filesystem": "npm:*" + "@types/har-format": "npm:*" + checksum: 10c0/6f784ec0239fc8921d3e84c8c93dbdb79209d27572cef839ff7866807ad50fcf6af9c62cd6b6bfeb2b623bfc10f54fbc400faf89844efe26c5ad5aa5e3f6adfc + languageName: node + linkType: hard + +"@wxt-dev/module-react@npm:^1.1.5": + version: 1.1.5 + resolution: "@wxt-dev/module-react@npm:1.1.5" + dependencies: + "@vitejs/plugin-react": "npm:^4.4.1 || ^5.0.0" + peerDependencies: + wxt: ">=0.19.16" + checksum: 10c0/d27c85c96a26817458af0f03ab1b4be59285d7941f349903cba825b0cb91e2fd197948fc25f9e9b812865adf4f100bffe2cc05d2b5b5b0080e53ec99a7c3a26a + languageName: node + linkType: hard + +"@wxt-dev/storage@npm:^1.0.0": + version: 1.2.6 + resolution: "@wxt-dev/storage@npm:1.2.6" + dependencies: + "@wxt-dev/browser": "npm:^0.1.4" + async-mutex: "npm:^0.5.0" + dequal: "npm:^2.0.3" + checksum: 10c0/dc66dacad878eca7d6143718f39c0ecbe32098df2ae3407506514a124d306eddf47f87158b37d3b32c98f7795b0e429b7609c596c6781c03622d06ca39e7ee2c + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"acorn@npm:^8.15.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"acorn@npm:^8.16.0": + version: 8.17.0 + resolution: "acorn@npm:8.17.0" + bin: + acorn: bin/acorn + checksum: 10c0/5dcefea5f8f023b6cc24cbe71fb5a8112b601d36c4fa07d14e4e6ffc2ee47383332c46b36c766d9437725aa6660156eae50efa0c838719823b50d7c327c4ed42 + languageName: node + linkType: hard + +"adm-zip@npm:~0.5.x": + version: 0.5.16 + resolution: "adm-zip@npm:0.5.16" + checksum: 10c0/6f10119d4570c7ba76dcf428abb8d3f69e63f92e51f700a542b43d4c0130373dd2ddfc8f85059f12d4a843703a90c3970cfd17876844b4f3f48bf042bfa6b49f + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ansi-align@npm:^3.0.1": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: "npm:^4.1.0" + checksum: 10c0/ad8b755a253a1bc8234eb341e0cec68a857ab18bf97ba2bda529e86f6e30460416523e0ec58c32e5c21f0ca470d779503244892873a5895dbd0c39c788e82467 + languageName: node + linkType: hard + +"ansi-escapes@npm:^7.0.0": + version: 7.2.0 + resolution: "ansi-escapes@npm:7.2.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/b562fd995761fa12f33be316950ee58fda489e125d331bcd9131434969a2eb55dc14e9405f214dcf4697c9d67c576ba0baf6e8f3d52058bf9222c97560b220cb + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1, ansi-regex@npm:^6.2.2": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + +"aria-query@npm:5.3.0": + version: 5.3.0 + resolution: "aria-query@npm:5.3.0" + dependencies: + dequal: "npm:^2.0.3" + checksum: 10c0/2bff0d4eba5852a9dd578ecf47eaef0e82cc52569b48469b0aac2db5145db0b17b7a58d9e01237706d1e14b7a1b0ac9b78e9c97027ad97679dd8f91b85da1469 + languageName: node + linkType: hard + +"aria-query@npm:^5.0.0": + version: 5.3.2 + resolution: "aria-query@npm:5.3.2" + checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e + languageName: node + linkType: hard + +"array-differ@npm:^4.0.0": + version: 4.0.0 + resolution: "array-differ@npm:4.0.0" + checksum: 10c0/72c035c505a7629d2983827a16654d73db6a9a2d6340ba9d0803aed516f46a202f3b7042c5a4a57534952f7477ca5394f3b65ecb9be5192e5d269f445f066d75 + languageName: node + linkType: hard + +"array-union@npm:^3.0.1": + version: 3.0.1 + resolution: "array-union@npm:3.0.1" + checksum: 10c0/b5271d7e5688d2d1932928b271796dbbddc422448557ab05ef6f34a9f84fb645eb855384feec6234bf59c226053a0e21b8a00b0e6cd588874b90a5c13dbeb64e + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"async-mutex@npm:^0.5.0": + version: 0.5.0 + resolution: "async-mutex@npm:0.5.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/9096e6ad6b674c894d8ddd5aa4c512b09bb05931b8746ebd634952b05685608b2b0820ed5c406e6569919ff5fe237ab3c491e6f2887d6da6b6ba906db3ee9c32 + languageName: node + linkType: hard + +"async@npm:^3.2.0": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a + languageName: node + linkType: hard + +"atomically@npm:^2.0.3": + version: 2.1.0 + resolution: "atomically@npm:2.1.0" + dependencies: + stubborn-fs: "npm:^2.0.0" + when-exit: "npm:^2.1.4" + checksum: 10c0/c352ce2e247e4f9aec4e5f46b9720a3598a8241ba4ac7d067060cfd418051268976fec5c958b9d13cc1139b9f11076a8cbdb7837c2e15ca34d3dcceb9c1000b5 + languageName: node + linkType: hard + +"autoprefixer@npm:^10.4.20": + version: 10.4.23 + resolution: "autoprefixer@npm:10.4.23" + dependencies: + browserslist: "npm:^4.28.1" + caniuse-lite: "npm:^1.0.30001760" + fraction.js: "npm:^5.3.4" + picocolors: "npm:^1.1.1" + postcss-value-parser: "npm:^4.2.0" + peerDependencies: + postcss: ^8.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 10c0/3765c5d0fa3e95fb2ebe9d5a6d4da0156f5d346c7ec9ac0fbf5c97c8139d0ca1e8743bf5dc1b4aa954467be6929fddf8498a3b6202d468d70b5f359f3b6af90f + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.9.0": + version: 2.10.40 + resolution: "baseline-browser-mapping@npm:2.10.40" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/5d3547aa9333b71f6239db89ef9d4aaf32ec0ee6cfa307025842722b5d225026995185ae4fc1e12e84a22199c905411bf3cce8076ae0a445b342982eb025171e + languageName: node + linkType: hard + +"bidi-js@npm:^1.0.3": + version: 1.0.3 + resolution: "bidi-js@npm:1.0.3" + dependencies: + require-from-string: "npm:^2.0.2" + checksum: 10c0/fdddea4aa4120a34285486f2267526cd9298b6e8b773ad25e765d4f104b6d7437ab4ba542e6939e3ac834a7570bcf121ee2cf6d3ae7cd7082c4b5bedc8f271e1 + languageName: node + linkType: hard + +"bluebird@npm:~3.7": + version: 3.7.2 + resolution: "bluebird@npm:3.7.2" + checksum: 10c0/680de03adc54ff925eaa6c7bb9a47a0690e8b5de60f4792604aae8ed618c65e6b63a7893b57ca924beaf53eee69c5af4f8314148c08124c550fe1df1add897d2 + languageName: node + linkType: hard + +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 10c0/e4b53deb4f2b85c52be0e21a273f2045c7b6a6ea002b0e139c744cb6f95e9ec044439a52883b0d74dedd1ff3da55ed140cfdddfed7fb0cccbed373de5dce1bcf + languageName: node + linkType: hard + +"boxen@npm:^8.0.1": + version: 8.0.1 + resolution: "boxen@npm:8.0.1" + dependencies: + ansi-align: "npm:^3.0.1" + camelcase: "npm:^8.0.0" + chalk: "npm:^5.3.0" + cli-boxes: "npm:^3.0.0" + string-width: "npm:^7.2.0" + type-fest: "npm:^4.21.0" + widest-line: "npm:^5.0.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/8c54f9797bf59eec0b44c9043d9cb5d5b2783dc673e4650235e43a5155c43334e78ec189fd410cf92056c1054aee3758279809deed115b49e68f1a1c6b3faa32 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.2": + version: 5.0.5 + resolution: "brace-expansion@npm:5.0.5" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/4d238e14ed4f5cc9c07285550a41cef23121ca08ba99fa9eb5b55b580dcb6bf868b8210aa10526bdc9f8dc97f33ca2a7259039c4cc131a93042beddb424c48e3 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0, browserslist@npm:^4.28.1": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd + languageName: node + linkType: hard + +"buffer-equal-constant-time@npm:^1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + +"c12@npm:^3.3.4": + version: 3.3.4 + resolution: "c12@npm:3.3.4" + dependencies: + chokidar: "npm:^5.0.0" + confbox: "npm:^0.2.4" + defu: "npm:^6.1.6" + dotenv: "npm:^17.3.1" + exsolve: "npm:^1.0.8" + giget: "npm:^3.2.0" + jiti: "npm:^2.6.1" + ohash: "npm:^2.0.11" + pathe: "npm:^2.0.3" + perfect-debounce: "npm:^2.1.0" + pkg-types: "npm:^2.3.0" + rc9: "npm:^3.0.1" + peerDependencies: + magicast: "*" + peerDependenciesMeta: + magicast: + optional: true + checksum: 10c0/d305df0c6e219464b3460fdd3443e62590e7d0d2f331e450a44f29e31e7120a0c7a6a76b6212bc0cf9f41058e3545c4fd04b0f1c6ea8d549678ea6479d94d8ad + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cac@npm:^6.7.14 || ^7.0.0, cac@npm:^7.0.0": + version: 7.0.0 + resolution: "cac@npm:7.0.0" + checksum: 10c0/e9da33cb9f0425546ae92a450d479276f9969a050fe64f5d6fedf058bdd87f22a370797fe1c158e07655fa9fc183749df7cb2037431e3772faa7bee9919fb763 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.4 + resolution: "cacache@npm:20.0.4" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + checksum: 10c0/539bf4020e44ba9ca5afc2ec435623ed7e0dd80c020097677e6b4a0545df5cc9d20b473212d01209c8b4aea43c0d095af0bb6da97bcb991642ea6fac0d7c462b + languageName: node + linkType: hard + +"camelcase@npm:^5.0.0": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^8.0.0": + version: 8.0.0 + resolution: "camelcase@npm:8.0.0" + checksum: 10c0/56c5fe072f0523c9908cdaac21d4a3b3fb0f608fb2e9ba90a60e792b95dd3bb3d1f3523873ab17d86d146e94171305f73ef619e2f538bd759675bc4a14b4bff3 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001759, caniuse-lite@npm:^1.0.30001760": + version: 1.0.30001800 + resolution: "caniuse-lite@npm:1.0.30001800" + checksum: 10c0/107ad692b89ce3b6c060f39159a19577b6b171c678a9174fbe827a6f3b2c3c9cd67f1e46076aa4731cefbd21ba2da754ca2300c6a448c7a05bdda078f73fa3b0 + languageName: node + linkType: hard + +"chai@npm:^6.2.2": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + +"chalk@npm:^5.3.0": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 + languageName: node + linkType: hard + +"chokidar@npm:^5.0.0": + version: 5.0.0 + resolution: "chokidar@npm:5.0.0" + dependencies: + readdirp: "npm:^5.0.0" + checksum: 10c0/42fc907cb2a7ff5c9e220f84dae75380a77997f851c2a5e7865a2cf9ae45dd407a23557208cdcdbf3ac8c93341135a1748e4c48c31855f3bfa095e5159b6bdec + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"chrome-launcher@npm:1.2.0": + version: 1.2.0 + resolution: "chrome-launcher@npm:1.2.0" + dependencies: + "@types/node": "npm:*" + escape-string-regexp: "npm:^4.0.0" + is-wsl: "npm:^2.2.0" + lighthouse-logger: "npm:^2.0.1" + bin: + print-chrome-path: bin/print-chrome-path.cjs + checksum: 10c0/3598bedecf70e42babada1df4f1bfa37071906973044737ff91d0e9ab53c4fac8cabd7489edb8bd4fcd83a70ae50c671265453d62f612419b745bfab63875817 + languageName: node + linkType: hard + +"ci-info@npm:^4.4.0": + version: 4.4.0 + resolution: "ci-info@npm:4.4.0" + checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a + languageName: node + linkType: hard + +"citty@npm:^0.2.2": + version: 0.2.2 + resolution: "citty@npm:0.2.2" + checksum: 10c0/c896c9dcd187d2a16685706d11428dcdec9eb59aa13fe50aff7b12e1d3521f1bf434d6a5316fe75a341f8ba5ce1d2beceb84f7f261d018985a73d3f6f2a793e3 + languageName: node + linkType: hard + +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 + languageName: node + linkType: hard + +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 + languageName: node + linkType: hard + +"cli-truncate@npm:^5.2.0": + version: 5.2.0 + resolution: "cli-truncate@npm:5.2.0" + dependencies: + slice-ansi: "npm:^8.0.0" + string-width: "npm:^8.2.0" + checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471 + languageName: node + linkType: hard + +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10c0/35229b1bb48647e882104cac374c9a18e34bbf0bace0e2cf03000326b6ca3050d6b59545d91e17bfe3705f4a0e2988787aa5cde6331bf5cbbf0164732cef6492 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"commander@npm:2.9.0": + version: 2.9.0 + resolution: "commander@npm:2.9.0" + dependencies: + graceful-readlink: "npm:>= 1.0.0" + checksum: 10c0/56bcda1e47f453016ed25d9f300bed9e622842a5515802658adb62792fa2ff9af6ee3f9ff16e058d7b20aacc78fb3baa3e02f982414bae1fb5f198c7cb41d5ad + languageName: node + linkType: hard + +"commander@npm:^9.1.0": + version: 9.5.0 + resolution: "commander@npm:9.5.0" + checksum: 10c0/5f7784fbda2aaec39e89eb46f06a999e00224b3763dc65976e05929ec486e174fe9aac2655f03ba6a5e83875bd173be5283dc19309b7c65954701c02025b3c1d + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concat-stream@npm:^1.4.7": + version: 1.6.2 + resolution: "concat-stream@npm:1.6.2" + dependencies: + buffer-from: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^2.2.2" + typedarray: "npm:^0.0.6" + checksum: 10c0/2e9864e18282946dabbccb212c5c7cec0702745e3671679eb8291812ca7fd12023f7d8cb36493942a62f770ac96a7f90009dc5c82ad69893438371720fa92617 + languageName: node + linkType: hard + +"confbox@npm:^0.1.8": + version: 0.1.8 + resolution: "confbox@npm:0.1.8" + checksum: 10c0/fc2c68d97cb54d885b10b63e45bd8da83a8a71459d3ecf1825143dd4c7f9f1b696b3283e07d9d12a144c1301c2ebc7842380bdf0014e55acc4ae1c9550102418 + languageName: node + linkType: hard + +"confbox@npm:^0.2.2": + version: 0.2.2 + resolution: "confbox@npm:0.2.2" + checksum: 10c0/7c246588d533d31e8cdf66cb4701dff6de60f9be77ab54c0d0338e7988750ac56863cc0aca1b3f2046f45ff223a765d3e5d4977a7674485afcd37b6edf3fd129 + languageName: node + linkType: hard + +"confbox@npm:^0.2.4": + version: 0.2.4 + resolution: "confbox@npm:0.2.4" + checksum: 10c0/4c36af33d9df7034300c452f7b289179264493bd0671fa81b995a0d70dc897b1d37f1af10d3ffb187f178d17ba1ed2ba167ed0f599ba3a139c271205dd553f73 + languageName: node + linkType: hard + +"config-chain@npm:^1.1.11": + version: 1.1.13 + resolution: "config-chain@npm:1.1.13" + dependencies: + ini: "npm:^1.3.4" + proto-list: "npm:~1.2.1" + checksum: 10c0/39d1df18739d7088736cc75695e98d7087aea43646351b028dfabd5508d79cf6ef4c5bcd90471f52cd87ae470d1c5490c0a8c1a292fbe6ee9ff688061ea0963e + languageName: node + linkType: hard + +"configstore@npm:^7.0.0": + version: 7.1.0 + resolution: "configstore@npm:7.1.0" + dependencies: + atomically: "npm:^2.0.3" + dot-prop: "npm:^9.0.0" + graceful-fs: "npm:^4.2.11" + xdg-basedir: "npm:^5.1.0" + checksum: 10c0/98f74ee84eb7fea8361f588d2f0f8fbec2dd680a628bb1e50668cfd3001ea2584565d31de1d57f18ab498d339778701f9bc1e77a997107e8ff10abd8afb267a6 + languageName: node + linkType: hard + +"consola@npm:^3.4.2": + version: 3.4.2 + resolution: "consola@npm:3.4.2" + checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"css-select@npm:^5.1.0": + version: 5.2.2 + resolution: "css-select@npm:5.2.2" + dependencies: + boolbase: "npm:^1.0.0" + css-what: "npm:^6.1.0" + domhandler: "npm:^5.0.2" + domutils: "npm:^3.0.1" + nth-check: "npm:^2.0.1" + checksum: 10c0/d79fffa97106007f2802589f3ed17b8c903f1c961c0fc28aa8a051eee0cbad394d8446223862efd4c1b40445a6034f626bb639cf2035b0bfc468544177593c99 + languageName: node + linkType: hard + +"css-tree@npm:^3.0.0, css-tree@npm:^3.2.1": + version: 3.2.1 + resolution: "css-tree@npm:3.2.1" + dependencies: + mdn-data: "npm:2.27.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/1f65e9ccaa56112a4706d6f003dd43d777f0dbcf848e66fd320f823192533581f8dd58daa906cb80622658332d50284d6be13b87a6ab4556cbbfe9ef535bbf7e + languageName: node + linkType: hard + +"css-what@npm:^6.1.0": + version: 6.2.2 + resolution: "css-what@npm:6.2.2" + checksum: 10c0/91e24c26fb977b4ccef30d7007d2668c1c10ac0154cc3f42f7304410e9594fb772aea4f30c832d2993b132ca8d99338050866476210316345ec2e7d47b248a56 + languageName: node + linkType: hard + +"css.escape@npm:^1.5.1": + version: 1.5.1 + resolution: "css.escape@npm:1.5.1" + checksum: 10c0/5e09035e5bf6c2c422b40c6df2eb1529657a17df37fda5d0433d722609527ab98090baf25b13970ca754079a0f3161dd3dfc0e743563ded8cfa0749d861c1525 + languageName: node + linkType: hard + +"cssom@npm:^0.5.0": + version: 0.5.0 + resolution: "cssom@npm:0.5.0" + checksum: 10c0/8c4121c243baf0678c65dcac29b201ff0067dfecf978de9d5c83b2ff127a8fdefd2bfd54577f5ad8c80ed7d2c8b489ae01c82023545d010c4ecb87683fb403dd + languageName: node + linkType: hard + +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"data-urls@npm:^7.0.0": + version: 7.0.0 + resolution: "data-urls@npm:7.0.0" + dependencies: + whatwg-mimetype: "npm:^5.0.0" + whatwg-url: "npm:^16.0.0" + checksum: 10c0/08d88ef50d8966a070ffdaa703e1e4b29f01bb2da364dfbc1612b1c2a4caa8045802c9532d81347b21781100132addb36a585071c8323b12cce97973961dee9f + languageName: node + linkType: hard + +"debounce@npm:1.2.1": + version: 1.2.1 + resolution: "debounce@npm:1.2.1" + checksum: 10c0/6c9320aa0973fc42050814621a7a8a78146c1975799b5b3cc1becf1f77ba9a5aa583987884230da0842a03f385def452fad5d60db97c3d1c8b824e38a8edf500 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.4, debug@npm:^4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"debug@npm:~4.3.1": + version: 4.3.7 + resolution: "debug@npm:4.3.7" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b + languageName: node + linkType: hard + +"decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"decimal.js@npm:^10.6.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"default-browser-id@npm:^5.0.0": + version: 5.0.1 + resolution: "default-browser-id@npm:5.0.1" + checksum: 10c0/5288b3094c740ef3a86df9b999b04ff5ba4dee6b64e7b355c0fff5217752c8c86908d67f32f6cba9bb4f9b7b61a1b640c0a4f9e34c57e0ff3493559a625245ee + languageName: node + linkType: hard + +"default-browser@npm:^5.4.0": + version: 5.5.0 + resolution: "default-browser@npm:5.5.0" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/576593b617b17a7223014b4571bfe1c06a2581a4eb8b130985d90d253afa3f40999caec70eb0e5776e80d4af6a41cce91018cd3f86e57ad578bf59e46fb19abe + languageName: node + linkType: hard + +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"defu@npm:^6.1.4": + version: 6.1.4 + resolution: "defu@npm:6.1.4" + checksum: 10c0/2d6cc366262dc0cb8096e429368e44052fdf43ed48e53ad84cc7c9407f890301aa5fcb80d0995abaaf842b3949f154d060be4160f7a46cb2bc2f7726c81526f5 + languageName: node + linkType: hard + +"defu@npm:^6.1.6": + version: 6.1.7 + resolution: "defu@npm:6.1.7" + checksum: 10c0/e6635388103c8be3c574ac31302f6930e5e6eeedba32cb1b30cf993c7d9fb571aec2485446dfa23bfa63e55e66156fe109027a9695db82a50f931e91e8d4bedb + languageName: node + linkType: hard + +"dequal@npm:^2.0.3": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 + languageName: node + linkType: hard + +"destr@npm:^2.0.5": + version: 2.0.5 + resolution: "destr@npm:2.0.5" + checksum: 10c0/efabffe7312a45ad90d79975376be958c50069f1156b94c181199763a7f971e113bd92227c26b94a169c71ca7dbc13583b7e96e5164743969fc79e1ff153e646 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"dijkstrajs@npm:^1.0.1": + version: 1.0.3 + resolution: "dijkstrajs@npm:1.0.3" + checksum: 10c0/2183d61ac1f25062f3c3773f3ea8d9f45ba164a00e77e07faf8cc5750da966222d1e2ce6299c875a80f969190c71a0973042192c5624d5223e4ed196ff584c99 + languageName: node + linkType: hard + +"dom-accessibility-api@npm:^0.5.9": + version: 0.5.16 + resolution: "dom-accessibility-api@npm:0.5.16" + checksum: 10c0/b2c2eda4fae568977cdac27a9f0c001edf4f95a6a6191dfa611e3721db2478d1badc01db5bb4fa8a848aeee13e442a6c2a4386d65ec65a1436f24715a2f8d053 + languageName: node + linkType: hard + +"dom-accessibility-api@npm:^0.6.3": + version: 0.6.3 + resolution: "dom-accessibility-api@npm:0.6.3" + checksum: 10c0/10bee5aa514b2a9a37c87cd81268db607a2e933a050074abc2f6fa3da9080ebed206a320cbc123567f2c3087d22292853bdfdceaffdd4334ffe2af9510b29360 + languageName: node + linkType: hard + +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.2" + entities: "npm:^4.2.0" + checksum: 10c0/d5ae2b7110ca3746b3643d3ef60ef823f5f078667baf530cec096433f1627ec4b6fa8c072f09d079d7cda915fd2c7bc1b7b935681e9b09e591e1e15f4040b8e2 + languageName: node + linkType: hard + +"domelementtype@npm:^2.3.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 + languageName: node + linkType: hard + +"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: "npm:^2.3.0" + checksum: 10c0/bba1e5932b3e196ad6862286d76adc89a0dbf0c773e5ced1eb01f9af930c50093a084eff14b8de5ea60b895c56a04d5de8bbc4930c5543d029091916770b2d2a + languageName: node + linkType: hard + +"domutils@npm:^3.0.1, domutils@npm:^3.2.1": + version: 3.2.2 + resolution: "domutils@npm:3.2.2" + dependencies: + dom-serializer: "npm:^2.0.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + checksum: 10c0/47938f473b987ea71cd59e59626eb8666d3aa8feba5266e45527f3b636c7883cca7e582d901531961f742c519d7514636b7973353b648762b2e3bedbf235fada + languageName: node + linkType: hard + +"dot-prop@npm:^9.0.0": + version: 9.0.0 + resolution: "dot-prop@npm:9.0.0" + dependencies: + type-fest: "npm:^4.18.2" + checksum: 10c0/4bac49a2f559156811862ac92813906f70529c50da918eaab81b38dd869743c667d578e183607f5ae11e8ae2a02e43e98e32c8a37bc4cae76b04d5b576e3112f + languageName: node + linkType: hard + +"dotenv-expand@npm:^12.0.3": + version: 12.0.3 + resolution: "dotenv-expand@npm:12.0.3" + dependencies: + dotenv: "npm:^16.4.5" + checksum: 10c0/0824bdc74fc816a28b0744b7853a23e046602e9616c82121dfdae21ebc13c6e89afeb773e794e97c35d48be2be0a990fbca721ee3869a49c04210a58a3cf296f + languageName: node + linkType: hard + +"dotenv@npm:^16.4.5": + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc + languageName: node + linkType: hard + +"dotenv@npm:^17.2.4, dotenv@npm:^17.3.1": + version: 17.4.2 + resolution: "dotenv@npm:17.4.2" + checksum: 10c0/164f8e77a646c8446867d5b588d26ea6005c8ea7c5eb41cf926f6113d23f2191355f6e0cfd95ea9bab98394a5b0a3f1e51a8399711b666fe55cc7b0bd745f942 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.263": + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10c0/0732bdb891b657f2e43266a3db8cf86fff6cecdcc8d693a92beff214e136cb5c2ee7dc5945ed75fa1db16e16bad0c38695527a020d15f39e79084e0b2e447621 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"enhanced-resolve@npm:5.21.6": + version: 5.21.6 + resolution: "enhanced-resolve@npm:5.21.6" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.3.3" + checksum: 10c0/4991b0ee020ce534c824e8f191a2cf068b9206dc6c9aef5797d41db5c45036c868145c2f0badee6084de2cc3703c7ca76426b254c6b2eac0e0e670ab4a33e528 + languageName: node + linkType: hard + +"entities@npm:^4.2.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10c0/ed836ddac5acb34341094eb495185d527bd70e8632b6c0d59548cbfa23defdbae70b96f9a405c82904efa421230b5b3fd2283752447d737beffd3f3e6ee74414 + languageName: node + linkType: hard + +"entities@npm:^8.0.0": + version: 8.0.0 + resolution: "entities@npm:8.0.0" + checksum: 10c0/938e631664c19451823344a351aeeafd74fae2d5fa51e4d5b6ff635afaefd4bacf0f609989888c04c42733f46ffdac15211608267ebb02488005891a4793e94d + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + +"error-ex@npm:^1.3.2": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + +"es-module-lexer@npm:^2.0.0": + version: 2.2.0 + resolution: "es-module-lexer@npm:2.2.0" + checksum: 10c0/a20547903d389031f383afe2c4770e5402f70afb8e5c69562427cb4c384bfb806aa24e8b719b75f432bae327c9db2abb1f227c907bb3bc3d9d9350d42d6f91a3 + languageName: node + linkType: hard + +"es6-error@npm:4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10c0/357663fb1e845c047d548c3d30f86e005db71e122678f4184ced0693f634688c3f3ef2d7de7d4af732f734de01f528b05954e270f06aa7d133679fb9fe6600ef + languageName: node + linkType: hard + +"esbuild@npm:^0.27.1": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-goat@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-goat@npm:4.0.0" + checksum: 10c0/9d2a8314e2370f2dd9436d177f6b3b1773525df8f895c8f3e1acb716f5fd6b10b336cb1cd9862d4709b36eb207dbe33664838deca9c6d55b8371be4eebb972f6 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"eventemitter3@npm:^5.0.4": + version: 5.0.4 + resolution: "eventemitter3@npm:5.0.4" + checksum: 10c0/575b8cac8d709e1473da46f8f15ef311b57ff7609445a7c71af5cd42598583eee6f098fa7a593e30f27e94b8865642baa0689e8fa97c016f742abdb3b1bf6d9a + languageName: node + linkType: hard + +"expect-type@npm:^1.3.0": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"exsolve@npm:^1.0.7, exsolve@npm:^1.0.8": + version: 1.0.8 + resolution: "exsolve@npm:1.0.8" + checksum: 10c0/65e44ae05bd4a4a5d87cfdbbd6b8f24389282cf9f85fa5feb17ca87ad3f354877e6af4cd99e02fc29044174891f82d1d68c77f69234410eb8f163530e6278c67 + languageName: node + linkType: hard + +"fast-redact@npm:^3.1.1": + version: 3.5.0 + resolution: "fast-redact@npm:3.5.0" + checksum: 10c0/7e2ce4aad6e7535e0775bf12bd3e4f2e53d8051d8b630e0fa9e67f68cb0b0e6070d2f7a94b1d0522ef07e32f7c7cda5755e2b677a6538f1e9070ca053c42343a + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"filesize@npm:^11.0.17": + version: 11.0.19 + resolution: "filesize@npm:11.0.19" + checksum: 10c0/e63505d6a3f3669e4741faee6d4c11db06841aa32473945dafac48c5596d43fba10dd6e365f98842404a5fe4157d50969732e5157f96edf13a2bea7565a59420 + languageName: node + linkType: hard + +"find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"firefox-profile@npm:4.7.0": + version: 4.7.0 + resolution: "firefox-profile@npm:4.7.0" + dependencies: + adm-zip: "npm:~0.5.x" + fs-extra: "npm:^11.2.0" + ini: "npm:^4.1.3" + minimist: "npm:^1.2.8" + xml2js: "npm:^0.6.2" + bin: + firefox-profile: lib/cli.js + checksum: 10c0/032949c923336f843015757f1aba90d19b9f0d7277ba91705958a5579d55c98a424700388ac263a1dc67d6a942403e25090443703ff0f38347a20e9dbc40e1a3 + languageName: node + linkType: hard + +"form-data-encoder@npm:^4.1.0": + version: 4.1.0 + resolution: "form-data-encoder@npm:4.1.0" + checksum: 10c0/cbd655aa8ffff6f7c2733b1d8e95fa9a2fe8a88a90bde29fb54b8e02c9406e51f32a014bfe8297d67fbac9f77614d14a8b4bbc4fd0352838e67e97a881d06332 + languageName: node + linkType: hard + +"formdata-node@npm:^6.0.3": + version: 6.0.3 + resolution: "formdata-node@npm:6.0.3" + checksum: 10c0/9b8ada280c7b0c7314bed57fd50b3562f8825bd3ede6f6231b1bc7683b649e7f3ffb7b0f13d8e9e6cae8042ea21eaf497a7c676d2fe6dc63daefefaea4838240 + languageName: node + linkType: hard + +"fraction.js@npm:^5.3.4": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10c0/f90079fe9bfc665e0a07079938e8ff71115bce9462f17b32fc283f163b0540ec34dc33df8ed41bb56f028316b04361b9a9995b9ee9258617f8338e0b05c5f95a + languageName: node + linkType: hard + +"framer-motion@npm:^12.42.2": + version: 12.42.2 + resolution: "framer-motion@npm:12.42.2" + dependencies: + motion-dom: "npm:^12.42.2" + motion-utils: "npm:^12.39.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10c0/2e514e0e303c0c01bc7d6a1028f7722ec17fc307922c6770a8dc5f7782521c60254f9c265329b887a29b32e2c730742328ecae468be626406afa5db1e6c0aa01 + languageName: node + linkType: hard + +"fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.0": + version: 11.3.3 + resolution: "fs-extra@npm:11.3.3" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/984924ff4104e3e9f351b658a864bf3b354b2c90429f57aec0acd12d92c4e6b762cbacacdffb4e745b280adce882e1f980c485d9f02c453f769ab4e7fc646ce3 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"fx-runner@npm:1.4.0": + version: 1.4.0 + resolution: "fx-runner@npm:1.4.0" + dependencies: + commander: "npm:2.9.0" + shell-quote: "npm:1.7.3" + spawn-sync: "npm:1.0.15" + when: "npm:3.7.7" + which: "npm:1.2.4" + winreg: "npm:0.0.12" + bin: + fx-runner: bin/fx-runner + checksum: 10c0/32ab32c5b9f92deced7103ed03de0dee1dca2c51f2e1d545ad34bafe600fb7f634f717b4a2c2fdab20058341846682f4d867a7081f6a75e66d658425a551d37c + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1": + version: 1.4.0 + resolution: "get-east-asian-width@npm:1.4.0" + checksum: 10c0/4e481d418e5a32061c36fbb90d1b225a254cc5b2df5f0b25da215dcd335a3c111f0c2023ffda43140727a9cafb62dac41d022da82c08f31083ee89f714ee3b83 + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.5.0": + version: 1.6.0 + resolution: "get-east-asian-width@npm:1.6.0" + checksum: 10c0/7e72e9550fd49ca5b246f9af6bb2afc129c96412845ff6556b3274fd44817a381702ca17028efe9866b261a3d44254cbf21e6c90cf05b4b61675630af776d431 + languageName: node + linkType: hard + +"get-port-please@npm:^3.2.0": + version: 3.2.0 + resolution: "get-port-please@npm:3.2.0" + checksum: 10c0/7e48443110b463e76ef47efc381c9f16d78798f9ea9f6d928dad2b5cee53a199cf64e6e2f22603e5f8a1f742e3d4a144cd367f6ef82ac48759bfd2beb48ee9e5 + languageName: node + linkType: hard + +"giget@npm:^1.2.3 || ^2.0.0 || ^3.0.0, giget@npm:^3.2.0": + version: 3.3.0 + resolution: "giget@npm:3.3.0" + bin: + giget: dist/cli.mjs + checksum: 10c0/abde08865c25f6b90ff9d3d50fffe2c4459d56eb61616b6ab884cec522277c25cd41f1c2e6743df13cd0a20022bb46f7eed8f3a79302ecd7eba71fc52570332d + languageName: node + linkType: hard + +"glob-to-regexp@npm:^0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.6 + resolution: "glob@npm:13.0.6" + dependencies: + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a + languageName: node + linkType: hard + +"global-directory@npm:^4.0.1": + version: 4.0.1 + resolution: "global-directory@npm:4.0.1" + dependencies: + ini: "npm:4.1.1" + checksum: 10c0/f9cbeef41db4876f94dd0bac1c1b4282a7de9c16350ecaaf83e7b2dd777b32704cc25beeb1170b5a63c42a2c9abfade74d46357fe0133e933218bc89e613d4b2 + languageName: node + linkType: hard + +"gmail-alias-toolkit@workspace:.": + version: 0.0.0-use.local + resolution: "gmail-alias-toolkit@workspace:." + dependencies: + "@tailwindcss/postcss": "npm:^4.3.2" + "@tanstack/react-virtual": "npm:^3.14.5" + "@testing-library/dom": "npm:^10.4.1" + "@testing-library/jest-dom": "npm:^6.9.1" + "@testing-library/react": "npm:^16.3.2" + "@testing-library/user-event": "npm:^14.6.1" + "@types/qrcode": "npm:^1.5.6" + "@types/react": "npm:^19.2.7" + "@types/react-dom": "npm:^19.2.3" + "@vitejs/plugin-react": "npm:^6.0.3" + "@wxt-dev/auto-icons": "npm:^1.1.0" + "@wxt-dev/module-react": "npm:^1.1.5" + autoprefixer: "npm:^10.4.20" + clsx: "npm:^2.1.1" + jsdom: "npm:^29.1.1" + lucide-react: "npm:^0.468.0" + motion: "npm:^12.0.0" + next-themes: "npm:^0.4.6" + postcss: "npm:^8.5.10" + qrcode: "npm:^1.5.4" + react: "npm:^19.2.3" + react-dom: "npm:^19.2.3" + tailwind-merge: "npm:^2.6.0" + tailwindcss: "npm:^4" + typescript: "npm:^5.9.3" + vite: "npm:^8.1.1" + vitest: "npm:^4.1.9" + wxt: "npm:^0.20.27" + languageName: unknown + linkType: soft + +"graceful-fs@npm:4.2.10": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 10c0/4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graceful-readlink@npm:>= 1.0.0": + version: 1.0.1 + resolution: "graceful-readlink@npm:1.0.1" + checksum: 10c0/c53e703257e77f8a4495ff0d476c09aa413251acd26684f4544771b15e0ad361d1075b8f6d27b52af6942ea58155a9bbdb8125d717c70df27117460fee295a54 + languageName: node + linkType: hard + +"growly@npm:^1.3.0": + version: 1.3.0 + resolution: "growly@npm:1.3.0" + checksum: 10c0/3043bd5c064e87f89e8c9b66894ed09fd882c7fa645621a543b45b72f040c7241e25061207a858ab191be2fbdac34795ff57c2a40962b154a6b2908a5e509252 + languageName: node + linkType: hard + +"hookable@npm:^6.1.0": + version: 6.1.1 + resolution: "hookable@npm:6.1.1" + checksum: 10c0/bb46cd9ffc0a997af21febd97835da4e59a6989adec73dc3c215fcc44c7ac01de4781f251c3d420bf45862d2592a695f5f0de095b6f5df52db8afb5166938212 + languageName: node + linkType: hard + +"html-encoding-sniffer@npm:^6.0.0": + version: 6.0.0 + resolution: "html-encoding-sniffer@npm:6.0.0" + dependencies: + "@exodus/bytes": "npm:^1.6.0" + checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025 + languageName: node + linkType: hard + +"html-escaper@npm:^3.0.3": + version: 3.0.3 + resolution: "html-escaper@npm:3.0.3" + checksum: 10c0/a042fa4139127ff7546513e90ea39cc9161a1938ce90122dbc4260d4b7252c9aa8452f4509c0c2889901b8ae9a8699179150f1f99d3f80bcf7317573c5f08f4e + languageName: node + linkType: hard + +"htmlparser2@npm:^10.0.0": + version: 10.0.0 + resolution: "htmlparser2@npm:10.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.2.1" + entities: "npm:^6.0.0" + checksum: 10c0/47cfa37e529c86a7ba9a1e0e6f951ad26ef8ca5af898ab6e8916fa02c0264c1453b4a65f28b7b8a7f9d0d29b5a70abead8203bf8b3f07bc69407e85e7d9a68e4 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.2": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 + languageName: node + linkType: hard + +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039 + languageName: node + linkType: hard + +"import-meta-resolve@npm:^4.2.0": + version: 4.2.0 + resolution: "import-meta-resolve@npm:4.2.0" + checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"inherits@npm:^2.0.3, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ini@npm:4.1.1": + version: 4.1.1 + resolution: "ini@npm:4.1.1" + checksum: 10c0/7fddc8dfd3e63567d4fdd5d999d1bf8a8487f1479d0b34a1d01f28d391a9228d261e19abc38e1a6a1ceb3400c727204fce05725d5eb598dfcf2077a1e3afe211 + languageName: node + linkType: hard + +"ini@npm:^1.3.4, ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"ini@npm:^4.1.3": + version: 4.1.3 + resolution: "ini@npm:4.1.3" + checksum: 10c0/0d27eff094d5f3899dd7c00d0c04ea733ca03a8eb6f9406ce15daac1a81de022cb417d6eaff7e4342451ffa663389c565ffc68d6825eaf686bf003280b945764 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"is-absolute@npm:^0.1.7": + version: 0.1.7 + resolution: "is-absolute@npm:0.1.7" + dependencies: + is-relative: "npm:^0.1.0" + checksum: 10c0/ffa42b79866c16e54c00a98a94f1eaf4b5bf1debae5e321b80b24d529d9a1e8f47ec1bcdc2dd0773ea814c8facbe76680582d099a57c3d5775720adcc4071850 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10c0/e828365958d155f90c409cdbe958f64051d99e8aedc2c8c4cd7c89dcf35329daed42f7b99346f7828df013e27deb8f721cf9408ba878c76eb9e8290235fbcdcc + languageName: node + linkType: hard + +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": + version: 5.1.0 + resolution: "is-fullwidth-code-point@npm:5.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.1" + checksum: 10c0/c1172c2e417fb73470c56c431851681591f6a17233603a9e6f94b7ba870b2e8a5266506490573b607fb1081318589372034aa436aec07b465c2029c0bc7f07a4 + languageName: node + linkType: hard + +"is-in-ci@npm:^1.0.0": + version: 1.0.0 + resolution: "is-in-ci@npm:1.0.0" + bin: + is-in-ci: cli.js + checksum: 10c0/98f9cec4c35aece4cf731abf35ebf28359a9b0324fac810da05b842515d9ccb33b8999c1d9a679f0362e1a4df3292065c38d7f86327b1387fa667bc0150f4898 + languageName: node + linkType: hard + +"is-in-ssh@npm:^1.0.0": + version: 1.0.0 + resolution: "is-in-ssh@npm:1.0.0" + checksum: 10c0/fbb4c25d85c543df09997fbe7aeca410ae0c839c5825bba2d4c672df765e9ce0e7558e781b371c0a21d6ef9bbac39b31875617a68eaaea5504438d07db9a2ffa + languageName: node + linkType: hard + +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + +"is-installed-globally@npm:^1.0.0": + version: 1.0.0 + resolution: "is-installed-globally@npm:1.0.0" + dependencies: + global-directory: "npm:^4.0.1" + is-path-inside: "npm:^4.0.0" + checksum: 10c0/5f57745b6e75b2e9e707a26470d0cb74291d9be33c0fe0dc06c6955fe086bc2ca0a8960631b1ecb9677100eac90af33e911aec7a2c0b88097d702bfa3b76486d + languageName: node + linkType: hard + +"is-npm@npm:^6.0.0": + version: 6.1.0 + resolution: "is-npm@npm:6.1.0" + checksum: 10c0/2319580963e7b77f51b07d242064926894472e0b8aab7d4f67aa58a2032715a18c77844a2d963718b8ee1eac112ce4dbcd55a9d994f589d5994d46b57b5cdfda + languageName: node + linkType: hard + +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10c0/51188d7e2b1d907a9a5f7c18d99a90b60870b951ed87cf97595d9aaa429d4c010652c3350bcbf31182e7f4b0eab9a1860b43e16729b13cb1a44baaa6cdb64c46 + languageName: node + linkType: hard + +"is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + +"is-potential-custom-element-name@npm:^1.0.1": + version: 1.0.1 + resolution: "is-potential-custom-element-name@npm:1.0.1" + checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9 + languageName: node + linkType: hard + +"is-primitive@npm:^3.0.1": + version: 3.0.1 + resolution: "is-primitive@npm:3.0.1" + checksum: 10c0/2e3b6f029fabbdda467ea51ea4fdd00e6552434108b863a08f296638072c506a7c195089e3e31f83e7fc14bebbd1c5c9f872fe127c9284a7665c8227b47ffdd6 + languageName: node + linkType: hard + +"is-relative@npm:^0.1.0": + version: 0.1.3 + resolution: "is-relative@npm:0.1.3" + checksum: 10c0/91a4fe81b3b93ee220562e56e817b16c243a265d6c2daf9872ee583718db506b3b54036e852aedbb14ed693d7fc439e8836d0a5e44c56f450f730d074600c3ab + languageName: node + linkType: hard + +"is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10c0/a6fa2d370d21be487c0165c7a440d567274fbba1a817f2f0bfa41cc5e3af25041d84267baa22df66696956038a43973e72fca117918c91431920bdef490fa25e + languageName: node + linkType: hard + +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + +"is-wsl@npm:^3.1.1": + version: 3.1.1 + resolution: "is-wsl@npm:3.1.1" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/7e5023522bfb8f27de4de960b0d82c4a8146c0bddb186529a3616d78b5bbbfc19ef0c5fc60d0b3a3cc0bf95a415fbdedc18454310ea3049587c879b07ace5107 + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isexe@npm:^1.1.1": + version: 1.1.2 + resolution: "isexe@npm:1.1.2" + checksum: 10c0/a61c79949c6198046d147df44693dc645f3605f8d3078e3720cf048daa7d966c8b4890a39924cec8e948395a9b6b33051af9fd7264d8ad96a4a3f562a592e33f + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + +"jiti@npm:^2.6.1": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10c0/79b2e96a8e623f66c1b703b98ec1b8be4500e1d217e09b09e343471bbb9c105381b83edbb979d01cef18318cc45ce6e153571b6c83122170eefa531c64b6789b + languageName: node + linkType: hard + +"jiti@npm:^2.7.0": + version: 2.7.0 + resolution: "jiti@npm:2.7.0" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10c0/1b1e2310a490dce1aeea3da5f5dfe18273516c20ce48be2e98eb8ea452d5f3dcc8fd0cfd6d28b4052a24c5dbab6e3089b2d7e79f0bce7915b10d750929563c42 + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-tokens@npm:^9.0.1": + version: 9.0.1 + resolution: "js-tokens@npm:9.0.1" + checksum: 10c0/68dcab8f233dde211a6b5fd98079783cbcd04b53617c1250e3553ee16ab3e6134f5e65478e41d82f6d351a052a63d71024553933808570f04dbf828d7921e80e + languageName: node + linkType: hard + +"jsdom@npm:^29.1.1": + version: 29.1.1 + resolution: "jsdom@npm:29.1.1" + dependencies: + "@asamuzakjp/css-color": "npm:^5.1.11" + "@asamuzakjp/dom-selector": "npm:^7.1.1" + "@bramus/specificity": "npm:^2.4.2" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.1.3" + "@exodus/bytes": "npm:^1.15.0" + css-tree: "npm:^3.2.1" + data-urls: "npm:^7.0.0" + decimal.js: "npm:^10.6.0" + html-encoding-sniffer: "npm:^6.0.0" + is-potential-custom-element-name: "npm:^1.0.1" + lru-cache: "npm:^11.3.5" + parse5: "npm:^8.0.1" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^6.0.1" + undici: "npm:^7.25.0" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^8.0.1" + whatwg-mimetype: "npm:^5.0.0" + whatwg-url: "npm:^16.0.1" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10c0/20e2174b09d9d06393cb48e1392b7a1cb7191d6656a6f7b3b8fbf9853b4ab0ef60b4a42c2c55f71b55ca5da50ffa75bcdc6986210963182e7993c6f9cd4f499b + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^3.0.0": + version: 3.0.2 + resolution: "json-parse-even-better-errors@npm:3.0.2" + checksum: 10c0/147f12b005768abe9fab78d2521ce2b7e1381a118413d634a40e6d907d7d10f5e9a05e47141e96d6853af7cc36d2c834d0a014251be48791e037ff2f13d2b94b + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.2.0 + resolution: "jsonfile@npm:6.2.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7f4f43b08d1869ded8a6822213d13ae3b99d651151d77efd1557ced0889c466296a7d9684e397bd126acf5eb2cfcb605808c3e681d0fdccd2fe5a04b47e76c0d + languageName: node + linkType: hard + +"jsonwebtoken@npm:^9.0.3": + version: 9.0.3 + resolution: "jsonwebtoken@npm:9.0.3" + dependencies: + jws: "npm:^4.0.1" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10c0/6ca7f1e54886ea3bde7146a5a22b53847c46e25453c7f7307a69818b9a6ad48c390b2e59d5690fcfd03c529b01960060cc4bb0c686991d6edae2285dfd30f4ba + languageName: node + linkType: hard + +"jszip@npm:^3.10.1, jszip@npm:^3.2.2": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: "npm:~3.3.0" + pako: "npm:~1.0.2" + readable-stream: "npm:~2.3.6" + setimmediate: "npm:^1.0.5" + checksum: 10c0/58e01ec9c4960383fb8b38dd5f67b83ccc1ec215bf74c8a5b32f42b6e5fb79fada5176842a11409c4051b5b94275044851814a31076bf49e1be218d3ef57c863 + languageName: node + linkType: hard + +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" + dependencies: + buffer-equal-constant-time: "npm:^1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ab3ebc6598e10dc11419d4ed675c9ca714a387481466b10e8a6f3f65d8d9c9237e2826f2505280a739cf4cbcf511cb288eeec22b5c9c63286fc5a2e4f97e78cf + languageName: node + linkType: hard + +"jws@npm:^4.0.1": + version: 4.0.1 + resolution: "jws@npm:4.0.1" + dependencies: + jwa: "npm:^2.0.1" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/6be1ed93023aef570ccc5ea8d162b065840f3ef12f0d1bb3114cade844de7a357d5dc558201d9a65101e70885a6fa56b17462f520e6b0d426195510618a154d0 + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"ky@npm:^1.2.0": + version: 1.14.2 + resolution: "ky@npm:1.14.2" + checksum: 10c0/ea5f08660ec8574ad6b116338059d1f2bac42ee917d56df268758405edfb3d17d5baa78913ee289eb6de976f648b1ff7798d5f8a7732c61e85e59d8f10f16795 + languageName: node + linkType: hard + +"latest-version@npm:^9.0.0": + version: 9.0.0 + resolution: "latest-version@npm:9.0.0" + dependencies: + package-json: "npm:^10.0.0" + checksum: 10c0/643cfda3a58dfb3af221a2950e433393d28a5adbe225d1cbbb358dbcbb04e9f8dce15b892f8ae3e3156f50693428dbd7ca13a69edfbdfcd94e62519480d7041e + languageName: node + linkType: hard + +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: "npm:~3.0.5" + checksum: 10c0/56dd113091978f82f9dc5081769c6f3b947852ecf9feccaf83e14a123bc630c2301439ce6182521e5fbafbde88e88ac38314327a4e0493a1bea7e0699a7af808 + languageName: node + linkType: hard + +"lighthouse-logger@npm:^2.0.1": + version: 2.0.2 + resolution: "lighthouse-logger@npm:2.0.2" + dependencies: + debug: "npm:^4.4.1" + marky: "npm:^1.2.2" + checksum: 10c0/bbce3939a0359d5f1f84b7cc623f1ee3daf5a28e55b7b9bf7d461d906121e64fa6de290c53bd6bdd6068a67442fa39a7deb6f61da2e0e1721c39ec4cc80876b8 + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:1.32.0, lightningcss@npm:^1.32.0": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 + languageName: node + linkType: hard + +"lines-and-columns@npm:^2.0.3": + version: 2.0.4 + resolution: "lines-and-columns@npm:2.0.4" + checksum: 10c0/4db28bf065cd7ad897c0700f22d3d0d7c5ed6777e138861c601c496d545340df3fc19e18bd04ff8d95a246a245eb55685b82ca2f8c2ca53a008e9c5316250379 + languageName: node + linkType: hard + +"linkedom@npm:^0.18.12": + version: 0.18.12 + resolution: "linkedom@npm:0.18.12" + dependencies: + css-select: "npm:^5.1.0" + cssom: "npm:^0.5.0" + html-escaper: "npm:^3.0.3" + htmlparser2: "npm:^10.0.0" + uhyphen: "npm:^0.2.0" + peerDependencies: + canvas: ">= 2" + peerDependenciesMeta: + canvas: + optional: true + checksum: 10c0/d7e4f9f40e02da81effa4d462a1ea9e23c0ed2d478aa2f0a96279cf2b51b77e3e637d074c9d5d92877816ab9f4c098bcf5151c8ceb82855b60e9dbba2f91b143 + languageName: node + linkType: hard + +"listr2@npm:^10.1.0": + version: 10.2.2 + resolution: "listr2@npm:10.2.2" + dependencies: + cli-truncate: "npm:^5.2.0" + eventemitter3: "npm:^5.0.4" + log-update: "npm:^6.1.0" + rfdc: "npm:^1.4.1" + wrap-ansi: "npm:^10.0.0" + checksum: 10c0/537f453fae217b8d33aa6b8edc49fbc981aa0e0d82e4e05d705bead086ff1e81d372435f1f3c473055dbbf91ff04dc044a90f9b60dba6cca4d22671b319f4d6a + languageName: node + linkType: hard + +"local-pkg@npm:^1.1.2": + version: 1.1.2 + resolution: "local-pkg@npm:1.1.2" + dependencies: + mlly: "npm:^1.7.4" + pkg-types: "npm:^2.3.0" + quansync: "npm:^0.2.11" + checksum: 10c0/1bcfcc5528dea95cba3caa478126a348d3985aad9f69ecf7802c13efef90897e1c5ff7851974332c5e6d4a4698efe610fef758a068c8bc3feb5322aeb35d5993 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 10c0/7ca498b9b75bf602d04e48c0adb842dfc7d90f77bcb2a91a2b2be34a723ad24bc1c8b3683ec6b2552a90f216c723cdea530ddb11a3320e08fa38265703978f4b + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 + languageName: node + linkType: hard + +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 10c0/4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 10c0/2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: 10c0/09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 10c0/46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 + languageName: node + linkType: hard + +"log-update@npm:^6.1.0": + version: 6.1.0 + resolution: "log-update@npm:6.1.0" + dependencies: + ansi-escapes: "npm:^7.0.0" + cli-cursor: "npm:^5.0.0" + slice-ansi: "npm:^7.1.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/4b350c0a83d7753fea34dcac6cd797d1dc9603291565de009baa4aa91c0447eab0d3815a05c8ec9ac04fdfffb43c82adcdb03ec1fceafd8518e1a8c1cff4ff89 + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.7 + resolution: "lru-cache@npm:11.2.7" + checksum: 10c0/549cdb59488baa617135fc12159cafb1a97f91079f35093bb3bcad72e849fc64ace636d244212c181dfdf1a99bbfa90757ff303f98561958ee4d0f885d9bd5f7 + languageName: node + linkType: hard + +"lru-cache@npm:^11.3.5": + version: 11.5.1 + resolution: "lru-cache@npm:11.5.1" + checksum: 10c0/7b341cea79a8efe9c6a6f20c8757a77eca5b25d7ff983ccf4e11e547b81f6787824baa1c84705251dff84ab4ffac85717ac354b9d02e465f86a9f8b166409979 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"lucide-react@npm:^0.468.0": + version: 0.468.0 + resolution: "lucide-react@npm:0.468.0" + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + checksum: 10c0/da7640e4ad09d8987716e9124fd85c4a0c49b7658dbbf45db64547fc0f155f7e8b2d7f68115ada3e24012f3e1a4f209d7a87762ba4de5ca3dd753f6b68d8e622 + languageName: node + linkType: hard + +"lz-string@npm:^1.5.0": + version: 1.5.0 + resolution: "lz-string@npm:1.5.0" + bin: + lz-string: bin/bin.js + checksum: 10c0/36128e4de34791838abe979b19927c26e67201ca5acf00880377af7d765b38d1c60847e01c5ec61b1a260c48029084ab3893a3925fd6e48a04011364b089991b + languageName: node + linkType: hard + +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"magicast@npm:^0.5.2": + version: 0.5.3 + resolution: "magicast@npm:0.5.3" + dependencies: + "@babel/parser": "npm:^7.29.3" + "@babel/types": "npm:^7.29.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/e288c027ae5f2a794a59148cb114f4b60f1d5c03090de6c60b4d187f12d1de9158779cd7c39cea391609f4f10cd7ea737929f25f7ce44f7a96ba96ec1a477e39 + languageName: node + linkType: hard + +"make-error@npm:^1.3.2": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.5 + resolution: "make-fetch-happen@npm:15.0.5" + dependencies: + "@gar/promise-retry": "npm:^1.0.0" + "@npmcli/agent": "npm:^4.0.0" + "@npmcli/redact": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + ssri: "npm:^13.0.0" + checksum: 10c0/527580eb5e5476e6ad07a4e3bd017d13e935f4be815674b442081ae5a721c13d3af5715006619e6be79a85723067e047f83a0c9e699f41d8cec43609a8de4f7b + languageName: node + linkType: hard + +"many-keys-map@npm:^2.0.1": + version: 2.0.1 + resolution: "many-keys-map@npm:2.0.1" + checksum: 10c0/cba5a8f67e847441fdff77cf584301e8f2bd91851690dbfabd0561f083c6acccbf34655a951b1e793d63547599b2c279ddf1185547ea0fc1d169e95b38ba2634 + languageName: node + linkType: hard + +"marky@npm:^1.2.2": + version: 1.3.0 + resolution: "marky@npm:1.3.0" + checksum: 10c0/6619cdb132fdc4f7cd3e2bed6eebf81a38e50ff4b426bbfb354db68731e4adfebf35ebfd7c8e5a6e846cbf9b872588c4f76db25782caee8c1529ec9d483bf98b + languageName: node + linkType: hard + +"mdn-data@npm:2.27.1": + version: 2.27.1 + resolution: "mdn-data@npm:2.27.1" + checksum: 10c0/eb8abf5d22e4d1e090346f5e81b67d23cef14c83940e445da5c44541ad874dc8fb9f6ca236e8258c3a489d9fb5884188a4d7d58773adb9089ac2c0b966796393 + languageName: node + linkType: hard + +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d + languageName: node + linkType: hard + +"min-indent@npm:^1.0.0": + version: 1.0.1 + resolution: "min-indent@npm:1.0.1" + checksum: 10c0/7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c + languageName: node + linkType: hard + +"minimatch@npm:^10.2.2": + version: 10.2.4 + resolution: "minimatch@npm:10.2.4" + dependencies: + brace-expansion: "npm:^5.0.2" + checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.8": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.2 + resolution: "minipass-fetch@npm:5.0.2" + dependencies: + iconv-lite: "npm:^0.7.2" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^2.0.0" + minizlib: "npm:^3.0.1" + dependenciesMeta: + iconv-lite: + optional: true + checksum: 10c0/ce4ab9f21cfabaead2097d95dd33f485af8072fbc6b19611bce694965393453a1639d641c2bcf1c48f2ea7d41ea7fab8278373f1d0bee4e63b0a5b2cdd0ef649 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.7 + resolution: "minipass-flush@npm:1.0.7" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/960915c02aa0991662c37c404517dd93708d17f96533b2ca8c1e776d158715d8107c5ced425ffc61674c167d93607f07f48a83c139ce1057f8781e5dfb4b90c2 + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^2.0.0": + version: 2.0.0 + resolution: "minipass-sized@npm:2.0.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/f9201696a6f6d68610d04c9c83e3d2e5cb9c026aae1c8cbf7e17f386105cb79c1bb088dbc21bf0b1eb4f3fb5df384fd1e7aa3bf1f33868c416ae8c8a92679db8 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mlly@npm:^1.7.4": + version: 1.8.0 + resolution: "mlly@npm:1.8.0" + dependencies: + acorn: "npm:^8.15.0" + pathe: "npm:^2.0.3" + pkg-types: "npm:^1.3.1" + ufo: "npm:^1.6.1" + checksum: 10c0/f174b844ae066c71e9b128046677868e2e28694f0bbeeffbe760b2a9d8ff24de0748d0fde6fabe706700c1d2e11d3c0d7a53071b5ea99671592fac03364604ab + languageName: node + linkType: hard + +"mlly@npm:^1.8.2": + version: 1.8.2 + resolution: "mlly@npm:1.8.2" + dependencies: + acorn: "npm:^8.16.0" + pathe: "npm:^2.0.3" + pkg-types: "npm:^1.3.1" + ufo: "npm:^1.6.3" + checksum: 10c0/aa826683a6daddf2aef65f9c8142e362731cf8e415a5591faf92fd51040a76697e45ab6dbb7a3b38be74e0f8c464825a7eabe827750455c7472421953f5da733 + languageName: node + linkType: hard + +"motion-dom@npm:^12.42.2": + version: 12.42.2 + resolution: "motion-dom@npm:12.42.2" + dependencies: + motion-utils: "npm:^12.39.0" + checksum: 10c0/1bcee88df706e3aaa4e5b8e7d2ba0efd8e2304c142e7541411bbd6f03b0fbe8d25883ec157905fdebf284f51db02008245f6d4653d20a97de9acf44bbb7be79f + languageName: node + linkType: hard + +"motion-utils@npm:^12.39.0": + version: 12.39.0 + resolution: "motion-utils@npm:12.39.0" + checksum: 10c0/6d7a2a2cc0797b72410a666a9cc1c201c8e39bf9669670464e433fe1e72af5f0217154c869867b34fbadf3664cf222c0d022bbc4eed7927f201ae971918e7440 + languageName: node + linkType: hard + +"motion@npm:^12.0.0": + version: 12.42.2 + resolution: "motion@npm:12.42.2" + dependencies: + framer-motion: "npm:^12.42.2" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10c0/44c333efe50fadb8ede589e237694e05dd73bed09c66f26ebcf89ca8a7449e77e55a0d3f2df6c1bdc00ee8de7f4e6fe957ef219d8002008472d978f4979ddab5 + languageName: node + linkType: hard + +"ms@npm:^2.1.1, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"multimatch@npm:6.0.0": + version: 6.0.0 + resolution: "multimatch@npm:6.0.0" + dependencies: + "@types/minimatch": "npm:^3.0.5" + array-differ: "npm:^4.0.0" + array-union: "npm:^3.0.1" + minimatch: "npm:^3.0.4" + checksum: 10c0/e303c3d83a66bdffbe6bb50b7be000dd299f1928a602323adc92daef3c1028ef1ee4cabf7d2ac458e32096c5dac2a263209835464348faf8a8332d076b58c35a + languageName: node + linkType: hard + +"nano-spawn@npm:^2.0.0": + version: 2.1.0 + resolution: "nano-spawn@npm:2.1.0" + checksum: 10c0/3becc67ed9ab630b6572feab69a4ef468891ad1f89d5c8643f14a2044cf32ba64533033506208039b1e3d9ddcb2f5f4f87ec360f13b3c4f0774304aedf0f0290 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.12": + version: 3.3.15 + resolution: "nanoid@npm:3.3.15" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e0b12e3a1d361f74150fa4b25631d0ae29f7162dab01a12f0f1be1f53b7a2a219f9b729504e474d4821207d0fe349bd3c97569ab5cf7ec2fff6aa94711956c93 + languageName: node + linkType: hard + +"nanospinner@npm:^1.2.2": + version: 1.2.2 + resolution: "nanospinner@npm:1.2.2" + dependencies: + picocolors: "npm:^1.1.1" + checksum: 10c0/07264f63816a8ec24d84ffe216a605cf11dffd8b098d4c5e6790437304b47e10ce4fc341de8dbcfc1b59aa42107f9949c89bcc201239eb61a80e14b6b1a20c90 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"next-themes@npm:^0.4.6": + version: 0.4.6 + resolution: "next-themes@npm:0.4.6" + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + checksum: 10c0/83590c11d359ce7e4ced14f6ea9dd7a691d5ce6843fe2dc520fc27e29ae1c535118478d03e7f172609c41b1ef1b8da6b8dd2d2acd6cd79cac1abbdbd5b99f2c4 + languageName: node + linkType: hard + +"node-fetch-native@npm:^1.6.7": + version: 1.6.7 + resolution: "node-fetch-native@npm:1.6.7" + checksum: 10c0/8b748300fb053d21ca4d3db9c3ff52593d5e8f8a2d9fe90cbfad159676e324b954fdaefab46aeca007b5b9edab3d150021c4846444e4e8ab1f4e44cd3807be87 + languageName: node + linkType: hard + +"node-forge@npm:^1.3.1": + version: 1.3.3 + resolution: "node-forge@npm:1.3.3" + checksum: 10c0/9c6f53b0ebb34865872cf62a35b0aef8fb337e2efc766626c2e3a0040f4c02933bf29a62ba999eb44a2aca73bd512c4eda22705a47b94654b9fb8ed53db9a1db + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.2.0 + resolution: "node-gyp@npm:12.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 + languageName: node + linkType: hard + +"node-notifier@npm:10.0.1": + version: 10.0.1 + resolution: "node-notifier@npm:10.0.1" + dependencies: + growly: "npm:^1.3.0" + is-wsl: "npm:^2.2.0" + semver: "npm:^7.3.5" + shellwords: "npm:^0.1.1" + uuid: "npm:^8.3.2" + which: "npm:^2.0.2" + checksum: 10c0/8888f6c4c277c588e6be991019e32ebbf4abdd598151683de59b9f70c31e6bbbddf0e443ea373da44338ab82a958695dcf73035c96e336a398940228d59399eb + languageName: node + linkType: hard + +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: "npm:^1.0.0" + checksum: 10c0/5fee7ff309727763689cfad844d979aedd2204a817fbaaf0e1603794a7c20db28548d7b024692f953557df6ce4a0ee4ae46cd8ebd9b36cfb300b9226b567c479 + languageName: node + linkType: hard + +"nypm@npm:^0.6.5": + version: 0.6.8 + resolution: "nypm@npm:0.6.8" + dependencies: + citty: "npm:^0.2.2" + pathe: "npm:^2.0.3" + tinyexec: "npm:^1.2.4" + bin: + nypm: ./dist/cli.mjs + checksum: 10c0/2aed08d6a125c5559889ccdf67f4b4a6247aa751b6b554d2b2e92c992997127bc87bb535b7782ff328ab3275b6c5daad012e1e212de8f6d08a0e99bcb53646e6 + languageName: node + linkType: hard + +"obug@npm:^2.1.1": + version: 2.1.3 + resolution: "obug@npm:2.1.3" + checksum: 10c0/cb8187fed0a5fc8445507c950e89f3c1bd43895658c398b5803f6b7804dfa0c562975ecce1e67f3d9247d521452a5bfade9e0e951cc0326b7444272f7c24d25f + languageName: node + linkType: hard + +"ofetch@npm:^1.5.1": + version: 1.5.1 + resolution: "ofetch@npm:1.5.1" + dependencies: + destr: "npm:^2.0.5" + node-fetch-native: "npm:^1.6.7" + ufo: "npm:^1.6.1" + checksum: 10c0/97ebc600512ea0ab401e97c73313218cc53c9b530b32ec8c995c347b0c68887129993168d1753f527761a64c6f93a5d823ce1378ccec95fc65a606f323a79a6c + languageName: node + linkType: hard + +"ohash@npm:^2.0.11": + version: 2.0.11 + resolution: "ohash@npm:2.0.11" + checksum: 10c0/d07c8d79cc26da082c1a7c8d5b56c399dd4ed3b2bd069fcae6bae78c99a9bcc3ad813b1e1f49ca2f335292846d689c6141a762cf078727d2302a33d414e69c79 + languageName: node + linkType: hard + +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.2 + resolution: "on-exit-leak-free@npm:2.1.2" + checksum: 10c0/faea2e1c9d696ecee919026c32be8d6a633a7ac1240b3b87e944a380e8a11dc9c95c4a1f8fb0568de7ab8db3823e790f12bda45296b1d111e341aad3922a0570 + languageName: node + linkType: hard + +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 + languageName: node + linkType: hard + +"open@npm:^11.0.0": + version: 11.0.0 + resolution: "open@npm:11.0.0" + dependencies: + default-browser: "npm:^5.4.0" + define-lazy-prop: "npm:^3.0.0" + is-in-ssh: "npm:^1.0.0" + is-inside-container: "npm:^1.0.0" + powershell-utils: "npm:^0.1.0" + wsl-utils: "npm:^0.3.0" + checksum: 10c0/7aeeda4131268ed90f90e7728dda5c46bb0c6205b27a4be3e86ea33593e30dd393423e20e31c00802a8e635ef59becaee33ef9749a8ceb027567cd253e9e7b1e + languageName: node + linkType: hard + +"open@npm:^8.4.0": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: "npm:^2.0.0" + is-docker: "npm:^2.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 + languageName: node + linkType: hard + +"os-shim@npm:^0.1.2": + version: 0.1.3 + resolution: "os-shim@npm:0.1.3" + checksum: 10c0/eaa09098c0f6a3115b2d0c027927cba9c2706e362b7767021b7ac83d159f18806ac1e95786b496d1912ce1aea8a6866e526d3f18f075c7c719eb08a0ffb9177f + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-json@npm:^10.0.0": + version: 10.0.1 + resolution: "package-json@npm:10.0.1" + dependencies: + ky: "npm:^1.2.0" + registry-auth-token: "npm:^5.0.2" + registry-url: "npm:^6.0.1" + semver: "npm:^7.6.0" + checksum: 10c0/4a55648d820496326730a7b149fd3fd8382e96f3d6def5ec687f46b75063894acf06b21f79832b40bb094c821d97f532cb0f009f85c4102d0084b488d4f492d3 + languageName: node + linkType: hard + +"pako@npm:~1.0.2": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe + languageName: node + linkType: hard + +"parse-json@npm:7.1.1": + version: 7.1.1 + resolution: "parse-json@npm:7.1.1" + dependencies: + "@babel/code-frame": "npm:^7.21.4" + error-ex: "npm:^1.3.2" + json-parse-even-better-errors: "npm:^3.0.0" + lines-and-columns: "npm:^2.0.3" + type-fest: "npm:^3.8.0" + checksum: 10c0/a85ebc7430af7763fa52eb456d7efd35c35be5b06f04d8d80c37d0d33312ac6cdff12647acb9c95448dcc8b907dfafa81fb126e094aa132b0abc2a71b9df51d5 + languageName: node + linkType: hard + +"parse5@npm:^8.0.1": + version: 8.0.1 + resolution: "parse5@npm:8.0.1" + dependencies: + entities: "npm:^8.0.0" + checksum: 10c0/c3c1c5aab55f6e4be5245599790e56e64be7764a4a0edd7f98db4fe3bb380f63add752fa047dff0496446c25f4104f0c7c1967723de640bde92306a7bb67ed2f + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 + languageName: node + linkType: hard + +"pathe@npm:^2.0.1, pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"perfect-debounce@npm:^2.1.0": + version: 2.1.0 + resolution: "perfect-debounce@npm:2.1.0" + checksum: 10c0/c4f833816f249129cea996d60b1351b640cf06954ab4eecaca440234ef70f4aefe6483637049750f5b00e15a5036307ea77f831c9779d6ad878457680af49b6d + languageName: node + linkType: hard + +"picocolors@npm:1.1.1, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + +"pino-abstract-transport@npm:^2.0.0": + version: 2.0.0 + resolution: "pino-abstract-transport@npm:2.0.0" + dependencies: + split2: "npm:^4.0.0" + checksum: 10c0/02c05b8f2ffce0d7c774c8e588f61e8b77de8ccb5f8125afd4a7325c9ea0e6af7fb78168999657712ae843e4462bb70ac550dfd6284f930ee57f17f486f25a9f + languageName: node + linkType: hard + +"pino-std-serializers@npm:^7.0.0": + version: 7.0.0 + resolution: "pino-std-serializers@npm:7.0.0" + checksum: 10c0/73e694d542e8de94445a03a98396cf383306de41fd75ecc07085d57ed7a57896198508a0dec6eefad8d701044af21eb27253ccc352586a03cf0d4a0bd25b4133 + languageName: node + linkType: hard + +"pino@npm:9.7.0": + version: 9.7.0 + resolution: "pino@npm:9.7.0" + dependencies: + atomic-sleep: "npm:^1.0.0" + fast-redact: "npm:^3.1.1" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^2.0.0" + pino-std-serializers: "npm:^7.0.0" + process-warning: "npm:^5.0.0" + quick-format-unescaped: "npm:^4.0.3" + real-require: "npm:^0.2.0" + safe-stable-stringify: "npm:^2.3.1" + sonic-boom: "npm:^4.0.1" + thread-stream: "npm:^3.0.0" + bin: + pino: bin.js + checksum: 10c0/c7f8a83a9a9d728b4eff6d0f4b9367f031c91bcaa5806fbf0eedcc8e77faba593d59baf11a8fba0dd1c778bb17ca7ed01418ac1df4ec129faeedd4f3ecaff66f + languageName: node + linkType: hard + +"pkg-types@npm:^1.3.1": + version: 1.3.1 + resolution: "pkg-types@npm:1.3.1" + dependencies: + confbox: "npm:^0.1.8" + mlly: "npm:^1.7.4" + pathe: "npm:^2.0.1" + checksum: 10c0/19e6cb8b66dcc66c89f2344aecfa47f2431c988cfa3366bdfdcfb1dd6695f87dcce37fbd90fe9d1605e2f4440b77f391e83c23255347c35cf84e7fd774d7fcea + languageName: node + linkType: hard + +"pkg-types@npm:^2.3.0": + version: 2.3.0 + resolution: "pkg-types@npm:2.3.0" + dependencies: + confbox: "npm:^0.2.2" + exsolve: "npm:^1.0.7" + pathe: "npm:^2.0.3" + checksum: 10c0/d2bbddc5b81bd4741e1529c08ef4c5f1542bbdcf63498b73b8e1d84cff71806d1b8b1577800549bb569cb7aa20056257677b979bff48c97967cba7e64f72ae12 + languageName: node + linkType: hard + +"pkg-types@npm:^2.3.1": + version: 2.3.1 + resolution: "pkg-types@npm:2.3.1" + dependencies: + confbox: "npm:^0.2.4" + exsolve: "npm:^1.0.8" + pathe: "npm:^2.0.3" + checksum: 10c0/dd9b20682426abaddcac9dc786aa22542e12df15a03dea2afa7bf54da0933358c6c7f545c0c3c1c7b24a2904ab4a451bf4e34a50c6837e458359eea30c257ed4 + languageName: node + linkType: hard + +"pngjs@npm:^5.0.0": + version: 5.0.0 + resolution: "pngjs@npm:5.0.0" + checksum: 10c0/c074d8a94fb75e2defa8021e85356bf7849688af7d8ce9995b7394d57cd1a777b272cfb7c4bce08b8d10e71e708e7717c81fd553a413f21840c548ec9d4893c6 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 + languageName: node + linkType: hard + +"postcss@npm:^8.5.10, postcss@npm:^8.5.15, postcss@npm:^8.5.16": + version: 8.5.16 + resolution: "postcss@npm:8.5.16" + dependencies: + nanoid: "npm:^3.3.12" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/625de7a02f662f3a340964d14b487bd5097adf16f5f171e257d19005ba37aea8768ee446557500e88e91ca46b4d14d6cb4a0bf033c6ec0c8c0b660d85719f1ef + languageName: node + linkType: hard + +"powershell-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "powershell-utils@npm:0.1.0" + checksum: 10c0/a64713cf3583259c9ed6be211c06b4b19e8608bcb0f7f6287ffac0a95b8c7582b6b662eea0e201fd659492c8e9f9c5fd0bfc4579645c5add9c1a600075621c95 + languageName: node + linkType: hard + +"pretty-format@npm:^27.0.2": + version: 27.5.1 + resolution: "pretty-format@npm:27.5.1" + dependencies: + ansi-regex: "npm:^5.0.1" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^17.0.1" + checksum: 10c0/0cbda1031aa30c659e10921fa94e0dd3f903ecbbbe7184a729ad66f2b6e7f17891e8c7d7654c458fa4ccb1a411ffb695b4f17bbcd3fe075fabe181027c4040ed + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"process-warning@npm:^5.0.0": + version: 5.0.0 + resolution: "process-warning@npm:5.0.0" + checksum: 10c0/941f48863d368ec161e0b5890ba0c6af94170078f3d6b5e915c19b36fb59edb0dc2f8e834d25e0d375a8bf368a49d490f080508842168832b93489d17843ec29 + languageName: node + linkType: hard + +"promise-toolbox@npm:0.21.0": + version: 0.21.0 + resolution: "promise-toolbox@npm:0.21.0" + dependencies: + make-error: "npm:^1.3.2" + checksum: 10c0/f1de739b200113f17b49017d8de43c4f2d579a60fbf696201e9ae68a3b78d4d4d9f7777ebbc3745f0c427bd6f065aec6a40b98d1a4351807d165d15281b8a3a9 + languageName: node + linkType: hard + +"prompts@npm:^2.4.2": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"proto-list@npm:~1.2.1": + version: 1.2.4 + resolution: "proto-list@npm:1.2.4" + checksum: 10c0/b9179f99394ec8a68b8afc817690185f3b03933f7b46ce2e22c1930dc84b60d09f5ad222beab4e59e58c6c039c7f7fcf620397235ef441a356f31f9744010e12 + languageName: node + linkType: hard + +"publish-browser-extension@npm:^2.3.0 || ^3.0.2 || ^4.0.5": + version: 4.0.5 + resolution: "publish-browser-extension@npm:4.0.5" + dependencies: + cac: "npm:^6.7.14" + consola: "npm:^3.4.2" + dotenv: "npm:^17.2.4" + form-data-encoder: "npm:^4.1.0" + formdata-node: "npm:^6.0.3" + jsonwebtoken: "npm:^9.0.3" + listr2: "npm:^10.1.0" + ofetch: "npm:^1.5.1" + zod: "npm:3.25.76 || ^4.3.6" + bin: + publish-extension: bin/publish-extension.mjs + checksum: 10c0/290195f5b04beecf54d840d36d1cf77da47f88f15e575ced759b8f49c70168b6d8d807a958a7012cbd70f15e31dd4dca82e6cfc9eebeaa97311a77996e77ef43 + languageName: node + linkType: hard + +"punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pupa@npm:^3.1.0": + version: 3.3.0 + resolution: "pupa@npm:3.3.0" + dependencies: + escape-goat: "npm:^4.0.0" + checksum: 10c0/9707e0a7f00e5922d47527d1c8d88d4224b1e86502da2fca27943eb0e9bb218121c91fa0af6c30531a2ee5ade0c326b5d33c40fdf61bc593c4224027412fd9b7 + languageName: node + linkType: hard + +"qrcode@npm:^1.5.4": + version: 1.5.4 + resolution: "qrcode@npm:1.5.4" + dependencies: + dijkstrajs: "npm:^1.0.1" + pngjs: "npm:^5.0.0" + yargs: "npm:^15.3.1" + bin: + qrcode: bin/qrcode + checksum: 10c0/ae1d57c9cff6099639a590b432c71b15e3bd3905ce4353e6d00c95dee6bb769a8f773f6a7575ecc1b8ed476bf79c5138a4a65cb380c682de3b926d7205d34d10 + languageName: node + linkType: hard + +"quansync@npm:^0.2.11": + version: 0.2.11 + resolution: "quansync@npm:0.2.11" + checksum: 10c0/cb9a1f8ebce074069f2f6a78578873ffedd9de9f6aa212039b44c0870955c04a71c3b1311b5d97f8ac2f2ec476de202d0a5c01160cb12bc0a11b7ef36d22ef56 + languageName: node + linkType: hard + +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 10c0/fe5acc6f775b172ca5b4373df26f7e4fd347975578199e7d74b2ae4077f0af05baa27d231de1e80e8f72d88275ccc6028568a7a8c9ee5e7368ace0e18eff93a4 + languageName: node + linkType: hard + +"rc9@npm:^3.0.1": + version: 3.0.1 + resolution: "rc9@npm:3.0.1" + dependencies: + defu: "npm:^6.1.6" + destr: "npm:^2.0.5" + checksum: 10c0/f952f80a6008b1be8b89f06cfec83ecc948aceb4ec4fabc25144bc0fa88aa601beb838d86720ae2c623971c4643cfea272535d6df15e40055082a4b6e4a24277 + languageName: node + linkType: hard + +"rc@npm:1.2.8": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"react-dom@npm:^19.2.3": + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 + languageName: node + linkType: hard + +"react-is@npm:^17.0.1": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 10c0/2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 + languageName: node + linkType: hard + +"react-refresh@npm:^0.18.0": + version: 0.18.0 + resolution: "react-refresh@npm:0.18.0" + checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2 + languageName: node + linkType: hard + +"react@npm:^19.2.3": + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 + languageName: node + linkType: hard + +"readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"readdirp@npm:^5.0.0": + version: 5.0.0 + resolution: "readdirp@npm:5.0.0" + checksum: 10c0/faf1ec57cff2020f473128da3f8d2a57813cc3a08a36c38cae1c9af32c1579906cc50ba75578043b35bade77e945c098233665797cf9730ba3613a62d6e79219 + languageName: node + linkType: hard + +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: 10c0/23eea5623642f0477412ef8b91acd3969015a1501ed34992ada0e3af521d3c865bb2fe4cdbfec5fe4b505f6d1ef6a03e5c3652520837a8c3b53decff7e74b6a0 + languageName: node + linkType: hard + +"redent@npm:^3.0.0": + version: 3.0.0 + resolution: "redent@npm:3.0.0" + dependencies: + indent-string: "npm:^4.0.0" + strip-indent: "npm:^3.0.0" + checksum: 10c0/d64a6b5c0b50eb3ddce3ab770f866658a2b9998c678f797919ceb1b586bab9259b311407280bd80b804e2a7c7539b19238ae6a2a20c843f1a7fcff21d48c2eae + languageName: node + linkType: hard + +"registry-auth-token@npm:^5.0.2": + version: 5.1.0 + resolution: "registry-auth-token@npm:5.1.0" + dependencies: + "@pnpm/npm-conf": "npm:^2.1.0" + checksum: 10c0/316229bd8a4acc29a362a7a3862ff809e608256f0fd9e0b133412b43d6a9ea18743756a0ec5ee1467a5384e1023602b85461b3d88d1336b11879e42f7cf02c12 + languageName: node + linkType: hard + +"registry-url@npm:^6.0.1": + version: 6.0.1 + resolution: "registry-url@npm:6.0.1" + dependencies: + rc: "npm:1.2.8" + checksum: 10c0/66e2221c8113fc35ee9d23fe58cb516fc8d556a189fb8d6f1011a02efccc846c4c9b5075b4027b99a5d5c9ad1345ac37f297bea3c0ca30d607ec8084bf561b90 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10c0/db91467d9ead311b4111cbd73a4e67fa7820daed2989a32f7023785a2659008c6d119752d9c4ac011ae07e537eb86523adff99804c5fdb39cd3a017f9b401bb6 + languageName: node + linkType: hard + +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 + languageName: node + linkType: hard + +"rfdc@npm:^1.4.1": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 + languageName: node + linkType: hard + +"rolldown@npm:~1.1.3": + version: 1.1.3 + resolution: "rolldown@npm:1.1.3" + dependencies: + "@oxc-project/types": "npm:=0.137.0" + "@rolldown/binding-android-arm64": "npm:1.1.3" + "@rolldown/binding-darwin-arm64": "npm:1.1.3" + "@rolldown/binding-darwin-x64": "npm:1.1.3" + "@rolldown/binding-freebsd-x64": "npm:1.1.3" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.1.3" + "@rolldown/binding-linux-arm64-gnu": "npm:1.1.3" + "@rolldown/binding-linux-arm64-musl": "npm:1.1.3" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.1.3" + "@rolldown/binding-linux-s390x-gnu": "npm:1.1.3" + "@rolldown/binding-linux-x64-gnu": "npm:1.1.3" + "@rolldown/binding-linux-x64-musl": "npm:1.1.3" + "@rolldown/binding-openharmony-arm64": "npm:1.1.3" + "@rolldown/binding-wasm32-wasi": "npm:1.1.3" + "@rolldown/binding-win32-arm64-msvc": "npm:1.1.3" + "@rolldown/binding-win32-x64-msvc": "npm:1.1.3" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/6dae11bee45c56d000d5d2608ac78b2c7125b7f10337e0b0bbdee7290c352104f1f76072f8c0e6ccad331f51f1a131fc37faa179d9c4a10cc16abc87f85f6e86 + languageName: node + linkType: hard + +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: 10c0/ab826c57c20f244b2ee807704b1ef4ba7f566aa766481ae5922aac785e2570809e297c69afcccc3593095b538a8a77d26f2b2e9a1d9dffee24e0e039502d1a03 + languageName: node + linkType: hard + +"safe-buffer@npm:^5.0.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10c0/baea14971858cadd65df23894a40588ed791769db21bafb7fd7608397dbdce9c5aac60748abae9995e0fc37e15f2061980501e012cd48859740796bea2987f49 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"sax@npm:>=0.6.0": + version: 1.4.3 + resolution: "sax@npm:1.4.3" + checksum: 10c0/45bba07561d93f184a8686e1a543418ced8c844b994fbe45cc49d5cd2fc8ac7ec949dae38565e35e388ad0cca2b75997a29b6857c927bf6553da3f80ed0e4e62 + languageName: node + linkType: hard + +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" + dependencies: + xmlchars: "npm:^2.2.0" + checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"scule@npm:^1.3.0": + version: 1.3.0 + resolution: "scule@npm:1.3.0" + checksum: 10c0/5d1736daa10622c420f2aa74e60d3c722e756bfb139fa784ae5c66669fdfe92932d30ed5072e4ce3107f9c3053e35ad73b2461cb18de45b867e1d4dea63f8823 + languageName: node + linkType: hard + +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.3": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + +"semver@npm:^7.5.4": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + +"set-value@npm:4.1.0": + version: 4.1.0 + resolution: "set-value@npm:4.1.0" + dependencies: + is-plain-object: "npm:^2.0.4" + is-primitive: "npm:^3.0.1" + checksum: 10c0/dc186676b6cc0cfcf1656b8acdfe7a68591f0645dd2872250100817fb53e5e9298dc1727a95605ac03f82110e9b3820c90a0a02d84e0fb89f210922b08b37e02 + languageName: node + linkType: hard + +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 + languageName: node + linkType: hard + +"sharp@npm:^0.34.1": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": "npm:^1.0.0" + "@img/sharp-darwin-arm64": "npm:0.34.5" + "@img/sharp-darwin-x64": "npm:0.34.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-linux-arm": "npm:0.34.5" + "@img/sharp-linux-arm64": "npm:0.34.5" + "@img/sharp-linux-ppc64": "npm:0.34.5" + "@img/sharp-linux-riscv64": "npm:0.34.5" + "@img/sharp-linux-s390x": "npm:0.34.5" + "@img/sharp-linux-x64": "npm:0.34.5" + "@img/sharp-linuxmusl-arm64": "npm:0.34.5" + "@img/sharp-linuxmusl-x64": "npm:0.34.5" + "@img/sharp-wasm32": "npm:0.34.5" + "@img/sharp-win32-arm64": "npm:0.34.5" + "@img/sharp-win32-ia32": "npm:0.34.5" + "@img/sharp-win32-x64": "npm:0.34.5" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.7.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f + languageName: node + linkType: hard + +"shell-quote@npm:1.7.3": + version: 1.7.3 + resolution: "shell-quote@npm:1.7.3" + checksum: 10c0/cf997c325f49c4393a859074f1ee9ca3da7d9e1940225bab24a86f0266504c7d7e356b83f13c74932cb243d53125b5c8c57b714017c53490bf1fe10540422014 + languageName: node + linkType: hard + +"shellwords@npm:^0.1.1": + version: 0.1.1 + resolution: "shellwords@npm:0.1.1" + checksum: 10c0/7d66b28927e0b524b71b2e185651fcd88a70473a077dd230fbf86188380e948ffb36cea00832d78fc13c93cd15f6f52286fb05f2746b7580623ca1ec619eb004 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slice-ansi@npm:^7.1.0": + version: 7.1.2 + resolution: "slice-ansi@npm:7.1.2" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^5.0.0" + checksum: 10c0/36742f2eb0c03e2e81a38ed14d13a64f7b732fe38c3faf96cce0599788a345011e840db35f1430ca606ea3f8db2abeb92a8d25c2753a819e3babaa10c2e289a2 + languageName: node + linkType: hard + +"slice-ansi@npm:^8.0.0": + version: 8.0.0 + resolution: "slice-ansi@npm:8.0.0" + dependencies: + ansi-styles: "npm:^6.2.3" + is-fullwidth-code-point: "npm:^5.1.0" + checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"sonic-boom@npm:^4.0.1": + version: 4.2.0 + resolution: "sonic-boom@npm:4.2.0" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10c0/ae897e6c2cd6d3cb7cdcf608bc182393b19c61c9413a85ce33ffd25891485589f39bece0db1de24381d0a38fc03d08c9862ded0c60f184f1b852f51f97af9684 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"source-map-support@npm:0.5.21": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d + languageName: node + linkType: hard + +"source-map@npm:^0.6.0": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"source-map@npm:^0.7.4": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c + languageName: node + linkType: hard + +"spawn-sync@npm:1.0.15": + version: 1.0.15 + resolution: "spawn-sync@npm:1.0.15" + dependencies: + concat-stream: "npm:^1.4.7" + os-shim: "npm:^0.1.2" + checksum: 10c0/7c4b626a075940c7ffccbcf612a0ff88316fdb2266be40a824e90e60092278025f055e6f9895eae45ff828bae0add181cc88c70e6c32ca3ee38823110055f6eb + languageName: node + linkType: hard + +"split2@npm:^4.0.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 + languageName: node + linkType: hard + +"split@npm:~1.0.1": + version: 1.0.1 + resolution: "split@npm:1.0.1" + dependencies: + through: "npm:2" + checksum: 10c0/7f489e7ed5ff8a2e43295f30a5197ffcb2d6202c9cf99357f9690d645b19c812bccf0be3ff336fea5054cda17ac96b91d67147d95dbfc31fbb5804c61962af85 + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.1 + resolution: "ssri@npm:13.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/cf6408a18676c57ff2ed06b8a20dc64bb3e748e5c7e095332e6aecaa2b8422b1e94a739a8453bf65156a8a47afe23757ba4ab52d3ea3b62322dc40875763e17a + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^4.0.0-rc.1": + version: 4.1.0 + resolution: "std-env@npm:4.1.0" + checksum: 10c0/2e14b6b490db34cb969a48d9cf7c35bca4a47653914aac2814221baae7b867a5b15940d133625c391621971f98cd2266a5dc7036669960e883f1081db2a56558 + languageName: node + linkType: hard + +"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^7.0.0, string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string-width@npm:^8.2.0": + version: 8.2.1 + resolution: "string-width@npm:8.2.1" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/d467b4eaf4c40a01bb438a2620e77badd2456ffd5131c9973abe4f3acf7c802d5b21f3b6a00a5e33a7fc28ca8f9c103226e01bac61e9f259659c6f46d78e353a + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.1.0": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b + languageName: node + linkType: hard + +"strip-ansi@npm:^7.1.2": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 + languageName: node + linkType: hard + +"strip-bom@npm:5.0.0": + version: 5.0.0 + resolution: "strip-bom@npm:5.0.0" + checksum: 10c0/f87e212f8a41e08e77d3994b3d9c4112258bd3a13688f9c7c85a6705a87a8e526422bce0762326cc2d9655c32a8c0ff1a2b14936795384c353828e4637823bc6 + languageName: node + linkType: hard + +"strip-indent@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-indent@npm:3.0.0" + dependencies: + min-indent: "npm:^1.0.0" + checksum: 10c0/ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679 + languageName: node + linkType: hard + +"strip-json-comments@npm:5.0.2": + version: 5.0.2 + resolution: "strip-json-comments@npm:5.0.2" + checksum: 10c0/e9841b8face78a01b0eb66f81e0a3419186a96f1d26817a5e1f5260b0631c10e0a7f711dddc5988edf599e5c079e4dd6e91defd21523e556636ba5679786f5ac + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"strip-literal@npm:^3.1.0": + version: 3.1.0 + resolution: "strip-literal@npm:3.1.0" + dependencies: + js-tokens: "npm:^9.0.1" + checksum: 10c0/50918f669915d9ad0fe4b7599902b735f853f2201c97791ead00104a654259c0c61bc2bc8fa3db05109339b61f4cf09e47b94ecc874ffbd0e013965223893af8 + languageName: node + linkType: hard + +"stubborn-fs@npm:^2.0.0": + version: 2.0.0 + resolution: "stubborn-fs@npm:2.0.0" + dependencies: + stubborn-utils: "npm:^1.0.1" + checksum: 10c0/31a60c9b2a61ce380b688f2649acaeff63cb0f2503bb6820c3ccd4f3af584c6310a48efa41b40c16b1717f1728572ed887c2c88650955c776a088228797e8d0e + languageName: node + linkType: hard + +"stubborn-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "stubborn-utils@npm:1.0.2" + checksum: 10c0/e65c5820d02c993df55c88e938796c2fb2f3a6d3dc247c961d1e4be4d6d88c355283f4b74157e89d1a1a761d66b5ae79560a416384241a7d3b4e8ba8f1ff5a78 + languageName: node + linkType: hard + +"symbol-tree@npm:^3.2.4": + version: 3.2.4 + resolution: "symbol-tree@npm:3.2.4" + checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509 + languageName: node + linkType: hard + +"tailwind-merge@npm:^2.6.0": + version: 2.6.1 + resolution: "tailwind-merge@npm:2.6.1" + checksum: 10c0/f9b5d7ba37f6c6dc7bb7a090f08252e8d827b5abfc1031bf468c5274ce104409e7952a0075a3e009aab53adda8c6d133bc1dd9d3427e2ae5bc00306f9ce1fbff + languageName: node + linkType: hard + +"tailwindcss@npm:4.3.2, tailwindcss@npm:^4": + version: 4.3.2 + resolution: "tailwindcss@npm:4.3.2" + checksum: 10c0/5a377846f77590812df234446175c4c2a45111a9626fd135e46f4552a33faee0d10c4e51aa5bbec955583265d48b380fb66c96f5da2775c961d4c26157617d19 + languageName: node + linkType: hard + +"tapable@npm:^2.3.3": + version: 2.3.3 + resolution: "tapable@npm:2.3.3" + checksum: 10c0/47992e861053f861154e92fb4a98ac4ab47b6463717e60792dd1e8c755da0c4964cd8bb68c308a9066d6da89000b6310457b4d5d985c30de4ccc29066068cc17 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.13 + resolution: "tar@npm:7.5.13" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b + languageName: node + linkType: hard + +"thread-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "thread-stream@npm:3.1.0" + dependencies: + real-require: "npm:^0.2.0" + checksum: 10c0/c36118379940b77a6ef3e6f4d5dd31e97b8210c3f7b9a54eb8fe6358ab173f6d0acfaf69b9c3db024b948c0c5fd2a7df93e2e49151af02076b35ada3205ec9a6 + languageName: node + linkType: hard + +"through@npm:2": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: 10c0/4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.2, tinyexec@npm:^1.2.4": + version: 1.2.4 + resolution: "tinyexec@npm:1.2.4" + checksum: 10c0/153b8db6b080194b558ff145b9cffc36b80a6e07babd644dcfbe49c807eee668c876049d28bdee90b96304476f883352f2dad91b3f86bc23832532f4363e66ff + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.16, tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"tinyrainbow@npm:^3.1.0": + version: 3.1.0 + resolution: "tinyrainbow@npm:3.1.0" + checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511 + languageName: node + linkType: hard + +"tldts-core@npm:^7.4.5": + version: 7.4.5 + resolution: "tldts-core@npm:7.4.5" + checksum: 10c0/7b6077980cba29fb65f56432c889b6b58ce6b2bc1ddb86ce5b13abfc2a6caec67e955f57084e96fcb92caa5eb60565a2474db79ee5d48555f13091d1a5485067 + languageName: node + linkType: hard + +"tldts@npm:^7.0.5": + version: 7.4.5 + resolution: "tldts@npm:7.4.5" + dependencies: + tldts-core: "npm:^7.4.5" + bin: + tldts: bin/cli.js + checksum: 10c0/c2b4c0b6015330c9ca73f9e6f334d555799a7119137f13bfad871ffaa1545b4136d765edfe84334585ffa5ad4c881bc707748673533b0acd5b476197dab1aff8 + languageName: node + linkType: hard + +"tmp@npm:0.2.5": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10c0/cee5bb7d674bb4ba3ab3f3841c2ca7e46daeb2109eec395c1ec7329a91d52fcb21032b79ac25161a37b2565c4858fefab927af9735926a113ef7bac9091a6e0e + languageName: node + linkType: hard + +"tough-cookie@npm:^6.0.1": + version: 6.0.1 + resolution: "tough-cookie@npm:6.0.1" + dependencies: + tldts: "npm:^7.0.5" + checksum: 10c0/ec70bd6b1215efe4ed31a158f0be3e4c9088fcbd8620edc23a5860d4f3d85c757b77e274baaa700f7b25e409f4181552ed189603c2b2e1a9f88104da3a61a37d + languageName: node + linkType: hard + +"tr46@npm:^6.0.0": + version: 6.0.0 + resolution: "tr46@npm:6.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/83130df2f649228aa91c17754b66248030a3af34911d713b5ea417066fa338aa4bc8668d06bd98aa21a2210f43fc0a3db8b9099e7747fb5830e40e39a6a1058e + languageName: node + linkType: hard + +"tslib@npm:^2.4.0, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"type-fest@npm:^3.8.0": + version: 3.13.1 + resolution: "type-fest@npm:3.13.1" + checksum: 10c0/547d22186f73a8c04590b70dcf63baff390078c75ea8acd366bbd510fd0646e348bd1970e47ecf795b7cff0b41d26e9c475c1fedd6ef5c45c82075fbf916b629 + languageName: node + linkType: hard + +"type-fest@npm:^4.18.2, type-fest@npm:^4.21.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 10c0/6005cb31df50eef8b1f3c780eb71a17925f3038a100d82f9406ac2ad1de5eb59f8e6decbdc145b3a1f8e5836e17b0c0002fb698b9fe2516b8f9f9ff602d36412 + languageName: node + linkType: hard + +"typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"ufo@npm:^1.6.1": + version: 1.6.1 + resolution: "ufo@npm:1.6.1" + checksum: 10c0/5a9f041e5945fba7c189d5410508cbcbefef80b253ed29aa2e1f8a2b86f4bd51af44ee18d4485e6d3468c92be9bf4a42e3a2b72dcaf27ce39ce947ec994f1e6b + languageName: node + linkType: hard + +"ufo@npm:^1.6.3": + version: 1.6.4 + resolution: "ufo@npm:1.6.4" + checksum: 10c0/3a2b29e7e3d772fbf6893d7d23bf442981457adb2fe122828abdbda89bedcb81aafd0dcc080e41b45f9a877db00cb42cbfee9639753a19d9b9bd39b5627039cf + languageName: node + linkType: hard + +"uhyphen@npm:^0.2.0": + version: 0.2.0 + resolution: "uhyphen@npm:0.2.0" + checksum: 10c0/1e7129fe7a5c86445d1adf04d5c58913b5992e4899ea5553d9ddf6e7ef88af0f807a47f1bf9673b92f705276e5cf1b2c1d3852f1ab5d08ecac3382bcc3a642f9 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + +"undici@npm:^7.25.0": + version: 7.28.0 + resolution: "undici@npm:7.28.0" + checksum: 10c0/fe781983a26098795e99bb1f64906cbb7d0bcaa029a26baade007b53ea67f2631d189b8f9671a31f4c8d0cb3773b7559608628ba54452fef51fec90e7c78bb0d + languageName: node + linkType: hard + +"unimport@npm:^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0": + version: 6.3.0 + resolution: "unimport@npm:6.3.0" + dependencies: + acorn: "npm:^8.16.0" + escape-string-regexp: "npm:^5.0.0" + estree-walker: "npm:^3.0.3" + local-pkg: "npm:^1.1.2" + magic-string: "npm:^0.30.21" + mlly: "npm:^1.8.2" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.4" + pkg-types: "npm:^2.3.1" + scule: "npm:^1.3.0" + strip-literal: "npm:^3.1.0" + tinyglobby: "npm:^0.2.16" + unplugin: "npm:^3.0.0" + unplugin-utils: "npm:^0.3.1" + peerDependencies: + oxc-parser: "*" + rolldown: ^1.0.0 + peerDependenciesMeta: + oxc-parser: + optional: true + rolldown: + optional: true + checksum: 10c0/a71c6017afd553cba1d66a297d5847ec7eb56f13c3657963358fede6340fa44d0d4ade4b4f56ba3d3b1f84b81ba045ebc314f5087b2c2bea845b4079bffb7844 + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a + languageName: node + linkType: hard + +"unplugin-utils@npm:^0.3.1": + version: 0.3.1 + resolution: "unplugin-utils@npm:0.3.1" + dependencies: + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + checksum: 10c0/e563b15f2ae604d4f84ac664a7b1738585d2e82a068e59612589e61e555b3d93aa7379a4b6938df3788fe5658cae53d752dd72f6072bd4a642b6e0385c0e4eab + languageName: node + linkType: hard + +"unplugin@npm:^3.0.0": + version: 3.3.0 + resolution: "unplugin@npm:3.3.0" + dependencies: + "@jridgewell/remapping": "npm:^2.3.5" + picomatch: "npm:^4.0.4" + webpack-virtual-modules: "npm:^0.6.2" + peerDependencies: + "@farmfe/core": "*" + "@rspack/core": "*" + bun-types-no-globals: "*" + esbuild: "*" + rolldown: "*" + rollup: "*" + unloader: "*" + vite: "*" + webpack: "*" + peerDependenciesMeta: + "@farmfe/core": + optional: true + "@rspack/core": + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true + checksum: 10c0/86ee7c7fde40ffa8260ddeba5da51800e35c6cdd41ee8a556ec3827357b5d2ee13dcbe4ea6820451bc6c2b05b35165f96c7bf20662114af5ed52183a74886404 + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + +"update-notifier@npm:7.3.1": + version: 7.3.1 + resolution: "update-notifier@npm:7.3.1" + dependencies: + boxen: "npm:^8.0.1" + chalk: "npm:^5.3.0" + configstore: "npm:^7.0.0" + is-in-ci: "npm:^1.0.0" + is-installed-globally: "npm:^1.0.0" + is-npm: "npm:^6.0.0" + latest-version: "npm:^9.0.0" + pupa: "npm:^3.1.0" + semver: "npm:^7.6.3" + xdg-basedir: "npm:^5.1.0" + checksum: 10c0/678839453840f46bb75e8cfebc0ff522262d2d3ece343fca722dd506039832e2a952d14ae39153f05f684467c8293ebc4c6479c9652c1bf97908fcaf300c2b31 + languageName: node + linkType: hard + +"util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard + +"vite-node@npm:^3.2.4 || ^5.0.0 || ^6.0.0": + version: 6.0.0 + resolution: "vite-node@npm:6.0.0" + dependencies: + cac: "npm:^7.0.0" + es-module-lexer: "npm:^2.0.0" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + vite: "npm:^8.0.0" + bin: + vite-node: dist/cli.mjs + checksum: 10c0/e586b17dcd6326dc746ba3f0655e6abaaece3b08c9570407cc24d3c238123679b43b05d37b57959afb05376e5b9cf152a0c62b0de8351c738c38cd042f4d7b14 + languageName: node + linkType: hard + +"vite@npm:^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0, vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0, vite@npm:^8.0.0, vite@npm:^8.1.1": + version: 8.1.1 + resolution: "vite@npm:8.1.1" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.32.0" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.16" + rolldown: "npm:~1.1.3" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/ad93a802d001b23f11ad9426f05fa5afa4f06c87eee713bbf20b13f5971178a20af7e738ddc2e2b0137b9819ae256ef80a4856299b2969f4173f8bda186d1456 + languageName: node + linkType: hard + +"vitest@npm:^4.1.9": + version: 4.1.9 + resolution: "vitest@npm:4.1.9" + dependencies: + "@vitest/expect": "npm:4.1.9" + "@vitest/mocker": "npm:4.1.9" + "@vitest/pretty-format": "npm:4.1.9" + "@vitest/runner": "npm:4.1.9" + "@vitest/snapshot": "npm:4.1.9" + "@vitest/spy": "npm:4.1.9" + "@vitest/utils": "npm:4.1.9" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^4.0.0-rc.1" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.1.9 + "@vitest/browser-preview": 4.1.9 + "@vitest/browser-webdriverio": 4.1.9 + "@vitest/coverage-istanbul": 4.1.9 + "@vitest/coverage-v8": 4.1.9 + "@vitest/ui": 4.1.9 + happy-dom: "*" + jsdom: "*" + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vite: + optional: false + bin: + vitest: ./vitest.mjs + checksum: 10c0/1ac80ef4991be82822a52aea48415f1bc64ddf8fd88ee24c172ec368f1d480fefacbde622c3c951982f7961a1d07313e18deaafc774d29e42ad6f6ffa63334a7 + languageName: node + linkType: hard + +"w3c-xmlserializer@npm:^5.0.0": + version: 5.0.0 + resolution: "w3c-xmlserializer@npm:5.0.0" + dependencies: + xml-name-validator: "npm:^5.0.0" + checksum: 10c0/8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b + languageName: node + linkType: hard + +"watchpack@npm:2.4.4": + version: 2.4.4 + resolution: "watchpack@npm:2.4.4" + dependencies: + glob-to-regexp: "npm:^0.4.1" + graceful-fs: "npm:^4.1.2" + checksum: 10c0/6c0901f75ce245d33991225af915eea1c5ae4ba087f3aee2b70dd377d4cacb34bef02a48daf109da9d59b2d31ec6463d924a0d72f8618ae1643dd07b95de5275 + languageName: node + linkType: hard + +"web-ext-run@npm:^0.2.4": + version: 0.2.4 + resolution: "web-ext-run@npm:0.2.4" + dependencies: + "@babel/runtime": "npm:7.28.2" + "@devicefarmer/adbkit": "npm:3.3.8" + chrome-launcher: "npm:1.2.0" + debounce: "npm:1.2.1" + es6-error: "npm:4.1.1" + firefox-profile: "npm:4.7.0" + fx-runner: "npm:1.4.0" + multimatch: "npm:6.0.0" + node-notifier: "npm:10.0.1" + parse-json: "npm:7.1.1" + pino: "npm:9.7.0" + promise-toolbox: "npm:0.21.0" + set-value: "npm:4.1.0" + source-map-support: "npm:0.5.21" + strip-bom: "npm:5.0.0" + strip-json-comments: "npm:5.0.2" + tmp: "npm:0.2.5" + update-notifier: "npm:7.3.1" + watchpack: "npm:2.4.4" + zip-dir: "npm:2.0.0" + checksum: 10c0/ac902a8b8aff07dc44c56a779161579aadfe0e8eecd79d6034f7c46e00fa13c0b58d5e69fbce84afb091a531efdba660b4fbf248cfbcc0ad91c8fe424dc04227 + languageName: node + linkType: hard + +"webidl-conversions@npm:^8.0.1": + version: 8.0.1 + resolution: "webidl-conversions@npm:8.0.1" + checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46 + languageName: node + linkType: hard + +"webpack-virtual-modules@npm:^0.6.2": + version: 0.6.2 + resolution: "webpack-virtual-modules@npm:0.6.2" + checksum: 10c0/5ffbddf0e84bf1562ff86cf6fcf039c74edf09d78358a6904a09bbd4484e8bb6812dc385fe14330b715031892dcd8423f7a88278b57c9f5002c84c2860179add + languageName: node + linkType: hard + +"whatwg-mimetype@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-mimetype@npm:5.0.0" + checksum: 10c0/eead164fe73a00dd82f817af6fc0bd22e9c273e1d55bf4bc6bdf2da7ad8127fca82ef00ea6a37892f5f5641f8e34128e09508f92126086baba126b9e0d57feb4 + languageName: node + linkType: hard + +"whatwg-url@npm:^16.0.0, whatwg-url@npm:^16.0.1": + version: 16.0.1 + resolution: "whatwg-url@npm:16.0.1" + dependencies: + "@exodus/bytes": "npm:^1.11.0" + tr46: "npm:^6.0.0" + webidl-conversions: "npm:^8.0.1" + checksum: 10c0/e75565566abf3a2cdbd9f06c965dbcccee6ec4e9f0d3728ad5e08ceb9944279848bcaa211d35a29cb6d2df1e467dd05cfb59fbddf8a0adcd7d0bce9ffb703fd2 + languageName: node + linkType: hard + +"when-exit@npm:^2.1.4": + version: 2.1.5 + resolution: "when-exit@npm:2.1.5" + checksum: 10c0/7db41b28c98456b784c25780ca327653f233c6eb7b25d4ce251d04519828cbd609fb6d10548caf9031d4d6fab2999d6f6911c32e1731efab24c93a522573470d + languageName: node + linkType: hard + +"when@npm:3.7.7": + version: 3.7.7 + resolution: "when@npm:3.7.7" + checksum: 10c0/2385c08ea86e74060248acf607526e75addf64ad7c5bae5563a42b7afa2dbf181d7fd8a247f27fdb7ccac9768e765805489f47242f99082ece765805f5cb3e3d + languageName: node + linkType: hard + +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10c0/087038e7992649eaffa6c7a4f3158d5b53b14cf5b6c1f0e043dccfacb1ba179d12f17545d5b85ebd94a42ce280a6fe65d0cbcab70f4fc6daad1dfae85e0e6a3e + languageName: node + linkType: hard + +"which@npm:1.2.4": + version: 1.2.4 + resolution: "which@npm:1.2.4" + dependencies: + is-absolute: "npm:^0.1.7" + isexe: "npm:^1.1.1" + bin: + which: ./bin/which + checksum: 10c0/618944508e04fefa02fa811b1a68d8a27b4f712f2f8332c27ed8bf8d1dc7e469bb9bbe20b4e197311ce798c16bb96b5c5e32ceaf275a3b5388bd8144536f5247 + languageName: node + linkType: hard + +"which@npm:^2.0.2": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.1 + resolution: "which@npm:6.0.1" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"widest-line@npm:^5.0.0": + version: 5.0.0 + resolution: "widest-line@npm:5.0.0" + dependencies: + string-width: "npm:^7.0.0" + checksum: 10c0/6bd6cca8cda502ef50e05353fd25de0df8c704ffc43ada7e0a9cf9a5d4f4e12520485d80e0b77cec8a21f6c3909042fcf732aa9281e5dbb98cc9384a138b2578 + languageName: node + linkType: hard + +"winreg@npm:0.0.12": + version: 0.0.12 + resolution: "winreg@npm:0.0.12" + checksum: 10c0/148b6aca1c3e88badd0d2b77ee0a71f1033e22e1cfcb41b71a5bba9e97cb3e7b6a2ec6b00cf0397959a13d65577d9173932588b3cd57b3f2e774b77ad14394ba + languageName: node + linkType: hard + +"wrap-ansi@npm:^10.0.0": + version: 10.0.0 + resolution: "wrap-ansi@npm:10.0.0" + dependencies: + ansi-styles: "npm:^6.2.3" + string-width: "npm:^8.2.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/6b163457630fe6d1c72aeed283a7410b2cc7487312df8b0ce96df3fbd64a2a7c948856ea97c25148c848627587c5c7945be474d8e723ab6011bb0756a53a9e89 + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + +"wsl-utils@npm:^0.3.0": + version: 0.3.1 + resolution: "wsl-utils@npm:0.3.1" + dependencies: + is-wsl: "npm:^3.1.0" + powershell-utils: "npm:^0.1.0" + checksum: 10c0/b3ba99cc6b71f66457eef598d529beeb8cb57a72646877fe25993894b808c60b82f6d47df5463f0b6e54632272f62f5eaea105c12784fd09b06f500f3f53aa2e + languageName: node + linkType: hard + +"wxt@npm:^0.20.27": + version: 0.20.27 + resolution: "wxt@npm:0.20.27" + dependencies: + "@1natsu/wait-element": "npm:^4.1.2" + "@aklinker1/rollup-plugin-visualizer": "npm:5.12.0" + "@webext-core/fake-browser": "npm:^1.3.4" + "@webext-core/isolated-element": "npm:^1.1.3" + "@webext-core/match-patterns": "npm:^1.0.3" + "@wxt-dev/browser": "npm:^0.2.0" + "@wxt-dev/storage": "npm:^1.0.0" + async-mutex: "npm:^0.5.0" + c12: "npm:^3.3.4" + cac: "npm:^6.7.14 || ^7.0.0" + chokidar: "npm:^5.0.0" + ci-info: "npm:^4.4.0" + consola: "npm:^3.4.2" + defu: "npm:^6.1.4" + dotenv-expand: "npm:^12.0.3" + esbuild: "npm:^0.27.1" + filesize: "npm:^11.0.17" + get-port-please: "npm:^3.2.0" + giget: "npm:^1.2.3 || ^2.0.0 || ^3.0.0" + hookable: "npm:^6.1.0" + import-meta-resolve: "npm:^4.2.0" + is-wsl: "npm:^3.1.1" + json5: "npm:^2.2.3" + jszip: "npm:^3.10.1" + linkedom: "npm:^0.18.12" + magicast: "npm:^0.5.2" + nano-spawn: "npm:^2.0.0" + nanospinner: "npm:^1.2.2" + normalize-path: "npm:^3.0.0" + nypm: "npm:^0.6.5" + ohash: "npm:^2.0.11" + open: "npm:^11.0.0" + perfect-debounce: "npm:^2.1.0" + picomatch: "npm:^4.0.3" + prompts: "npm:^2.4.2" + publish-browser-extension: "npm:^2.3.0 || ^3.0.2 || ^4.0.5" + scule: "npm:^1.3.0" + tinyglobby: "npm:^0.2.16" + unimport: "npm:^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0" + vite: "npm:^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0" + vite-node: "npm:^3.2.4 || ^5.0.0 || ^6.0.0" + web-ext-run: "npm:^0.2.4" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + bin: + wxt: bin/wxt.mjs + wxt-publish-extension: bin/wxt-publish-extension.mjs + checksum: 10c0/e6325406ea3d16da36c97a1a1a68ac1ba006f65d854bfee617d04e43cd9c808089ca1013a665dd92e58c34366c2f223061f9d17789475968f6108877740604ec + languageName: node + linkType: hard + +"xdg-basedir@npm:^5.1.0": + version: 5.1.0 + resolution: "xdg-basedir@npm:5.1.0" + checksum: 10c0/c88efabc71ffd996ba9ad8923a8cc1c7c020a03e2c59f0ffa72e06be9e724ad2a0fccef488757bc6ed3d8849d753dd25082d1035d95cb179e79eae4d034d0b80 + languageName: node + linkType: hard + +"xml-name-validator@npm:^5.0.0": + version: 5.0.0 + resolution: "xml-name-validator@npm:5.0.0" + checksum: 10c0/3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5 + languageName: node + linkType: hard + +"xml2js@npm:^0.6.2": + version: 0.6.2 + resolution: "xml2js@npm:0.6.2" + dependencies: + sax: "npm:>=0.6.0" + xmlbuilder: "npm:~11.0.0" + checksum: 10c0/e98a84e9c172c556ee2c5afa0fc7161b46919e8b53ab20de140eedea19903ed82f7cd5b1576fb345c84f0a18da1982ddf65908129b58fc3d7cbc658ae232108f + languageName: node + linkType: hard + +"xmlbuilder@npm:~11.0.0": + version: 11.0.1 + resolution: "xmlbuilder@npm:11.0.1" + checksum: 10c0/74b979f89a0a129926bc786b913459bdbcefa809afaa551c5ab83f89b1915bdaea14c11c759284bb9b931e3b53004dbc2181e21d3ca9553eeb0b2a7b4e40c35b + languageName: node + linkType: hard + +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 + languageName: node + linkType: hard + +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10c0/308a2efd7cc296ab2c0f3b9284fd4827be01cfeb647b3ba18230e3a416eb1bc887ac050de9f8c4fd9e7856b2e8246e05d190b53c96c5ad8d8cb56dffb6f81024 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10c0/25df918833592a83f52e7e4f91ba7d7bfaa2b891ebf7fe901923c2ee797534f23a176913ff6ff7ebbc1cc1725a044cc6a6539fed8bfd4e13b5b16376875f9499 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^15.3.1": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10c0/f1ca680c974333a5822732825cca7e95306c5a1e7750eb7b973ce6dc4f97a6b0a8837203c8b194f461969bfe1fb1176d1d423036635285f6010b392fa498ab2d + languageName: node + linkType: hard + +"yargs@npm:^17.5.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"zip-dir@npm:2.0.0": + version: 2.0.0 + resolution: "zip-dir@npm:2.0.0" + dependencies: + async: "npm:^3.2.0" + jszip: "npm:^3.2.2" + checksum: 10c0/3bc6f84caeaaa19e7a65be01b5f042332eb09ec4a609d4ebebd93f854dfd2deb635f4b4486de224c6bdcb7e4e88b5e98792ffd14f1c58ce9b196061a83560be6 + languageName: node + linkType: hard + +"zod@npm:3.25.76 || ^4.3.6": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 + languageName: node + linkType: hard