diff --git a/.agents/skills/frontend-designer.md b/.agents/skills/frontend-designer.md
new file mode 100644
index 0000000..afcbfd4
--- /dev/null
+++ b/.agents/skills/frontend-designer.md
@@ -0,0 +1,42 @@
+---
+name: frontend-design
+description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
+license: Complete terms in LICENSE.txt
+---
+
+This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
+
+The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
+
+## Design Thinking
+
+Before coding, understand the context and commit to a BOLD aesthetic direction:
+- **Purpose**: What problem does this interface solve? Who uses it?
+- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
+- **Constraints**: Technical requirements (framework, performance, accessibility).
+- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
+
+**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
+
+Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
+- Production-grade and functional
+- Visually striking and memorable
+- Cohesive with a clear aesthetic point-of-view
+- Meticulously refined in every detail
+
+## Frontend Aesthetics Guidelines
+
+Focus on:
+- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
+- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
+- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
+- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
+- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
+
+NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
+
+Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
+
+**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
+
+Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
diff --git a/.crush.json b/.crush.json
new file mode 100644
index 0000000..2ca2fe9
--- /dev/null
+++ b/.crush.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "https://charm.land/crush.json",
+ "permissions": {
+ "allowed_tools": [
+ "view",
+ "ls",
+ "grep",
+ "glob",
+ "edit",
+ "go",
+ "just",
+ "agent",
+ "agentic_fetch"
+ ]
+ },
+ "options": {
+ "initialize_as": "AGENTS.md",
+ "skills_paths": ["./.agents/skills"]
+ },
+ "lsp": {
+ "typescript": {
+ "command": "typescript-language-server",
+ "args": ["--stdio"]
+ },
+ "omnisharp": {
+ "command": "wsl",
+ "args": ["--", "bash", "-lc", "\"OmniSharp\""]
+ }
+ }
+}
diff --git a/.gitignore b/.gitignore
index 6facb8c..d914c32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,24 +1,41 @@
-# .NET
-bin/
-obj/
+# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
+
+# dependencies
+node_modules/
+
+# Expo
+.expo/
dist/
-*.dll
-*.exe
-*.pdb
+web-build/
+expo-env.d.ts
-# IDE
-.vs/
-.vscode/
-.idea/
-*.user
-*.suo
+# Native
+.kotlin/
+*.orig.*
+*.jks
+*.p8
+*.p12
+*.key
+*.mobileprovision
-# OS
+# Metro
+.metro-health-check*
+
+# debug
+npm-debug.*
+yarn-debug.*
+yarn-error.*
+
+# macOS
.DS_Store
-Thumbs.db
+*.pem
-# Project specific
-*.csv
-*.dlens
-*.apk
-debug/
+# local env files
+.env*.local
+
+# typescript
+*.tsbuildinfo
+
+# generated native folders
+/ios
+/android
diff --git a/.just/emu.just b/.just/emu.just
new file mode 100644
index 0000000..7152410
--- /dev/null
+++ b/.just/emu.just
@@ -0,0 +1,35 @@
+set shell := ["C:/Program Files/Git/usr/bin/bash.exe", "-c"]
+set unstable := true
+
+android_sdk := replace(env('LOCALAPPDATA'), '\', '/') / "Android/Sdk"
+adb := android_sdk / "platform-tools/adb.exe"
+avd := android_sdk / "cmdline-tools/latest/bin/avdmanager.bat"
+emulator := android_sdk / "emulator/emulator.exe"
+camera_virtual := "-camera-back virtualscene -virtualscene-poster wall=\"" + (justfile_directory() / "TestImages/reference_alpha/serra_angel.jpg") + "\""
+camera_webcam := "-camera-back webcam0 -camera-front webcam0"
+
+default camera="virtual":
+ {{ emulator }} -avd Pixel_6 {{ if camera == "virtual" { camera_virtual } else { camera_webcam } }} -no-snapshot-load -gpu host
+
+install:
+ {{ avd }} delete avd -n Pixel_6
+ {{ avd }} create avd -n Pixel_6 -k "system-images;android-36;google_apis_playstore;x86_64" -d pixel_6
+
+# Wait for emulator to fully boot (timeout after 2 minutes)
+[script]
+emu-wait:
+ # Wait for Android emulator to boot with timeout
+ TIMEOUT=120
+
+ echo "Waiting for emulator to boot..."
+
+ for ((i=TIMEOUT; i>0; i--)); do
+ if [ "$({{ adb }} shell getprop sys.boot_completed 2>/dev/null)" = "1" ]; then
+ echo "Emulator ready"
+ exit 0
+ fi
+ sleep 1
+ done
+
+ echo "Emulator failed to boot within 2 minutes"
+ exit 1
diff --git a/.justfile b/.justfile
index 055de56..df476fa 100644
--- a/.justfile
+++ b/.justfile
@@ -3,45 +3,17 @@
set shell := ["C:/Program Files/Git/usr/bin/bash.exe", "-c"]
set unstable := true
+mod emu '.just/emu.just'
+
# Android SDK paths
android_sdk := replace(env('LOCALAPPDATA'), '\', '/') / "Android/Sdk"
adb := android_sdk / "platform-tools/adb.exe"
-emulator := android_sdk / "emulator/emulator.exe"
-camera_virtual := "-camera-back virtualscene -virtualscene-poster wall=\"" + (justfile_directory() / "TestImages/reference_alpha/serra_angel.jpg") + "\""
-camera_webcam := "-camera-back webcam0 -camera-front webcam0"
[private]
@default:
just --list
-# Start emulator in background
-emu camera="virtual":
- {{ emulator }} -avd Pixel_6 {{ if camera == "virtual" { camera_virtual } else { camera_webcam } }} -no-snapshot-load -gpu host
-
-# Kill the running emulator
-emu-kill:
- {{ adb }} emu kill
-
-# Wait for emulator to fully boot (timeout after 2 minutes)
-[script]
-emu-wait:
- # Wait for Android emulator to boot with timeout
- TIMEOUT=120
-
- echo "Waiting for emulator to boot..."
-
- for ((i=TIMEOUT; i>0; i--)); do
- if [ "$({{ adb }} shell getprop sys.boot_completed 2>/dev/null)" = "1" ]; then
- echo "Emulator ready"
- exit 0
- fi
- sleep 1
- done
-
- echo "Emulator failed to boot within 2 minutes"
- exit 1
-
# Build a project
build project="src/Scry.App" target="net10.0-android":
@echo "Building {{ project }}..."
@@ -79,6 +51,30 @@ gen-db: (build "tools/DbGenerator" "net10.0")
dotnet run --project tools/DbGenerator --no-build -- src/Scry.App/Resources/Raw/card_hashes.db
@echo "Completed generating the database"
-# Full workflow: start emulator, wait, run with hot reload
+# Start Expo dev server with Convex (hot reload)
dev:
- dotnet watch --project src/Scry.App -f net10.0-android
+ bun run dev
+
+# Start just the Expo dev server
+start:
+ bun run dev:expo
+
+# Start just Convex dev server
+convex-dev:
+ bun run dev:convex
+
+# Run Expo app on Android emulator
+android:
+ bun run android
+
+# Install Expo app dependencies
+expo-install:
+ bun install
+
+# Run hash migration to Convex
+expo-migrate:
+ bun run migrate
+
+# TypeScript check for Expo app
+expo-typecheck:
+ bun run typecheck
diff --git a/AGENTS.md b/AGENTS.md
index a04e7bf..78b4cc7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,13 +2,15 @@
## Overview
-Scry is a Magic: The Gathering card scanner app built with .NET MAUI. It uses perceptual hashing to match photographed cards against a database of known card images from Scryfall.
+Scry is a Magic: The Gathering card scanner app built with Expo (React Native) and Convex. It uses perceptual hashing to match photographed cards against a database of known card images from Scryfall.
-**Key components:**
-- Mobile scanning app (MAUI/Android)
-- Card recognition via perceptual hashing (not OCR)
-- SQLite database with pre-computed hashes
-- Scryfall API integration for card data
+**Tech Stack:**
+- **Frontend**: Expo/React Native with Expo Router (file-based routing)
+- **Backend**: Convex (serverless functions + real-time database)
+- **Image Processing**: React Native Skia, fast-opencv
+- **Camera**: Adaptive (expo-camera in dev, react-native-vision-camera in production)
+- **Auth**: Convex Auth with Zitadel OIDC (GDPR-compliant, no PII stored)
+- **Package Manager**: Bun (not npm/yarn)
## Build Commands
@@ -16,55 +18,84 @@ Use `just` commands (defined in `.justfile`):
| Task | Command | Notes |
|------|---------|-------|
-| Build project | `just build` | Builds Android debug |
-| Run tests | `just test` | Runs xUnit tests |
-| Generate card database | `just gen-db` | Downloads from Scryfall, computes hashes |
-| Publish app | `just publish` | Creates release APK |
-| Hot reload dev | `just dev` | Uses `dotnet watch` |
-| Start emulator | `just emu` | Virtual camera with Serra Angel |
-| Install to device | `just install` | Installs release APK |
+| Start dev server | `just dev` | Runs Convex + Expo together |
+| Expo only | `just start` | Just the Expo dev server |
+| Convex only | `just convex-dev` | Just the Convex dev server |
+| Run on Android | `just android` | Starts Android emulator |
+| Install deps | `just expo-install` | Runs `bun install` |
+| Migrate hashes | `just expo-migrate` | Migrate card hashes to Convex |
+| Type check | `just expo-typecheck` | TypeScript check |
+| Start emulator | `just emu` | Virtual camera (submodule) |
-### Database Generator Options
+### Direct Bun Commands
```bash
-just gen-db # Default: 500 cards with test images
-dotnet run --project tools/DbGenerator -- -c 1000 # More cards
-dotnet run --project tools/DbGenerator -- --force # Rebuild from scratch
-dotnet run --project tools/DbGenerator -- --no-test-cards # Skip priority test cards
+bun install # Install dependencies
+bun run dev # Convex + Expo hot reload
+bun run dev:convex # Convex dev server only
+bun run dev:expo # Expo dev server only
+bun run android # Run on Android
+bun run migrate # Migrate hashes to Convex
+bun run typecheck # TypeScript check
+bunx convex dev # Convex CLI
```
## Project Structure
```
-src/
-├── Scry.App/ # MAUI mobile app (Android target)
-│ ├── Views/ # XAML pages (ScanPage, CollectionPage, etc.)
-│ ├── ViewModels/ # MVVM ViewModels using CommunityToolkit.Mvvm
-│ ├── Services/ # App-layer services (ICardRecognitionService, ICardRepository)
-│ ├── Converters/ # XAML value converters
-│ ├── Models/ # App-specific models (CollectionEntry)
-│ └── Resources/Raw/ # Bundled card_hashes.db
-│
-└── Scry.Core/ # Platform-independent core library
- ├── Recognition/ # CardRecognitionService, RecognitionOptions
- ├── Imaging/ # PerceptualHash, ImagePreprocessor, CardDetector
- ├── Data/ # CardDatabase (SQLite)
- ├── Models/ # Card, Oracle, Set, ScanResult
- └── Scryfall/ # ScryfallClient for API/bulk data
+app/ # Expo Router pages
+├── _layout.tsx # Root layout (Convex + HashCache providers)
+├── modal.tsx # Card details modal
+├── +not-found.tsx # 404 page
+└── (tabs)/ # Tab navigation group
+ ├── _layout.tsx # Tab bar layout
+ ├── index.tsx # Collection tab (home)
+ ├── scan.tsx # Camera scan tab
+ └── settings.tsx # Settings tab
-test/
-└── Scry.Tests/ # xUnit tests
+components/
+├── camera/ # Adaptive camera system
+│ ├── index.tsx # AdaptiveCamera wrapper
+│ ├── ExpoCamera.tsx # expo-camera (Expo Go)
+│ └── VisionCamera.tsx # react-native-vision-camera (production)
+└── *.tsx # Shared UI components
-tools/
-└── DbGenerator/ # CLI tool to generate card_hashes.db
+convex/ # Backend (Convex functions + schema)
+├── schema.ts # Database schema
+├── auth.ts # Zitadel OIDC configuration
+├── http.ts # HTTP endpoints for auth
+├── cards.ts # Card queries/mutations
+├── collections.ts # User collection functions
+├── users.ts # User functions
+├── scanHistory.ts # Scan history functions
+└── _generated/ # Auto-generated types
-TestImages/ # Test images organized by category
-├── reference_alpha/ # Alpha/Beta cards for testing
-├── single_cards/ # Individual card photos
-├── varying_quality/ # Different lighting/quality
-├── hands/ # Cards held in hand
-├── foil/ # Foil cards with glare
-└── ... # More categories
+lib/
+├── recognition/ # Card recognition pipeline
+│ ├── recognitionService.ts # Main recognition logic
+│ ├── cardDetector.ts # Edge detection, find card quad
+│ ├── perspectiveCorrection.ts # Warp to rectangle
+│ ├── clahe.ts # CLAHE lighting normalization
+│ ├── perceptualHash.ts # 192-bit color hash (24 bytes)
+│ ├── imageUtils.ts # Resize, rotate, grayscale
+│ ├── imageLoader.ts # Load/resize images
+│ └── skiaDecoder.ts # Decode images with Skia
+├── hooks/ # React hooks
+│ ├── useAuth.ts # OAuth flow with expo-auth-session
+│ ├── useCamera.ts # Adaptive camera permissions
+│ ├── useConvex.ts # Convex data hooks
+│ ├── useSync.ts # Hash sync hook
+│ └── useUserProfile.ts # Fetch profile from Zitadel
+├── context/ # React contexts
+│ └── HashCacheContext.tsx # In-memory hash cache
+└── db/ # Local database utilities
+ ├── localDatabase.ts # SQLite wrapper
+ └── syncService.ts # Sync with Convex
+
+scripts/
+└── migrate-hashes.ts # Migration script
+
+TestImages/ # Test images (225 files)
```
## Architecture
@@ -76,114 +107,67 @@ Camera Image
│
▼
┌─────────────────────┐
-│ CardDetector │ ← Edge detection, find card quad
+│ loadImageAsBase64 │ ← Resize to 480×640
+└─────────────────────┘
+ │
+ ▼
+┌─────────────────────┐
+│ decodeImageBase64 │ ← Skia decodes to RGBA pixels
+└─────────────────────┘
+ │
+ ▼
+┌─────────────────────┐
+│ detectCard │ ← Edge detection, find card quad
│ (optional) │
└─────────────────────┘
│
▼
┌─────────────────────┐
-│ PerspectiveCorrection│ ← Warp to rectangle
+│ warpPerspective │ ← Warp detected quad to rectangle
└─────────────────────┘
│
▼
┌─────────────────────┐
-│ ImagePreprocessor │ ← CLAHE for lighting normalization
-│ (ApplyClahe) │
+│ applyCLAHE │ ← Lighting normalization
└─────────────────────┘
│
▼
┌─────────────────────┐
-│ PerceptualHash │ ← Compute 192-bit color hash (24 bytes)
-│ (ComputeColorHash) │
+│ computeColorHash │ ← Compute 192-bit color hash (24 bytes)
└─────────────────────┘
│
▼
┌─────────────────────┐
-│ CardRecognitionService│ ← Hamming distance match against DB
+│ recognizeCard │ ← Hamming distance match against cache
└─────────────────────┘
```
-### Data Model
+### Data Model (Convex Schema)
-Three-table schema mirroring Scryfall:
-
-- **oracles** - Abstract game cards (one per unique card name)
-- **sets** - MTG sets with metadata
-- **cards** - Printings with perceptual hashes (one per unique artwork)
-
-The `Card` model includes denormalized Oracle fields for convenience.
-
-### Key Classes
-
-| Class | Purpose |
+| Table | Purpose |
|-------|---------|
-| `CardRecognitionService` | Main recognition logic, caches DB, handles rotation matching |
-| `PerceptualHash` | DCT-based color hash (192-bit = 8 bytes × 3 RGB channels) |
-| `ImagePreprocessor` | CLAHE, resize, grayscale conversions |
-| `CardDetector` | Edge detection + contour analysis to find card boundaries |
-| `PerspectiveCorrection` | Warp detected quad to rectangle |
-| `CardDatabase` | SQLite wrapper with batch insert, queries |
-| `ScryfallClient` | Bulk data streaming, image downloads |
+| `users` | Minimal auth (no PII, GDPR compliant) |
+| `cards` | Card printings with 24-byte perceptual hashes |
+| `oracles` | Abstract game cards (one per unique card name) |
+| `sets` | MTG sets with metadata |
+| `collections` | User card collections |
+| `scanHistory` | Scan history with confidence scores |
+| `metadata` | Sync metadata |
-## Code Conventions
+### GDPR Compliance
-### General
+- Database stores **no user PII** - only auth subject ID
+- User profile (name, email, image) fetched from Zitadel userinfo endpoint on demand
+- Profile held in memory only, never persisted
-- **Target**: .NET 10.0 (net10.0-android for app, net10.0 for Core/tools)
-- **Nullable**: Enabled everywhere (`enable`)
-- **Warnings as errors**: `true`
-- **Central package management**: Versions in `Directory.Packages.props`
+### Adaptive Camera System
-### C# Style
+The app detects its runtime environment and uses the appropriate camera:
-- Records for data models (`record Card`, `record ScanResult`)
-- `required` properties for non-nullable required fields
-- Extension methods for conversions (`ScryfallCard.ToCard()`)
-- Static classes for pure functions (`PerceptualHash`, `ImagePreprocessor`)
-- `using` declarations (not `using` blocks) for disposables
-- File-scoped namespaces
-- Primary constructors where appropriate
-- `CancellationToken` parameter on all async methods
+- **Expo Go** (`Constants.appOwnership === "expo"`): Uses `expo-camera`
+- **Production builds**: Uses `react-native-vision-camera`
-### MVVM (App layer)
-
-- `CommunityToolkit.Mvvm` for source generators
-- `[ObservableProperty]` attributes for bindable properties
-- `[RelayCommand]` for commands
-- ViewModels in `Scry.ViewModels` namespace
-
-### Naming
-
-- Services: `ICardRecognitionService`, `CardRecognitionService`
-- Database methods: `GetCardsWithHashAsync`, `InsertCardBatchAsync`
-- Hash methods: `ComputeColorHash`, `HammingDistance`
-- Test methods: `RecognizeAsync_ExactMatch_ReturnsSuccess`
-
-## Testing
-
-Tests are in `test/Scry.Tests` using xUnit.
-
-```bash
-just test # Run all tests
-dotnet test --filter "FullyQualifiedName~PerceptualHash" # Filter by name
-```
-
-### Test Categories
-
-| Test Class | Tests |
-|------------|-------|
-| `PerceptualHashTests` | Hash computation, Hamming distance |
-| `CardRecognitionTests` | End-to-end recognition |
-| `CardDatabaseTests` | SQLite CRUD operations |
-| `ImagePreprocessorTests` | CLAHE, resize |
-| `RobustnessAnalysisTests` | Multiple image variations |
-
-### Test Images
-
-TestImages directory contains categorized reference images:
-- `reference_alpha/` - Alpha/Beta cards (matching DbGenerator priority cards)
-- `single_cards/` - Clean single card photos
-- `varying_quality/` - Different lighting/blur conditions
+Both expose the same `CameraHandle` interface with `takePhoto()`.
## Key Algorithms
@@ -206,86 +190,99 @@ Applied in LAB color space to L channel only:
- Clip limit prevents over-amplification of noise
- Bilinear interpolation between tiles for smooth output
-### Card Detection
+## Code Conventions
-Pure SkiaSharp implementation:
-1. Grayscale → Gaussian blur → Canny edge detection
-2. Contour tracing via flood fill
-3. Douglas-Peucker simplification → Convex hull
-4. Find best quadrilateral matching MTG aspect ratio (88/63 ≈ 1.397)
-5. Order corners: top-left, top-right, bottom-right, bottom-left
+### General
-## Debug Mode
+- **TypeScript**: Strict mode enabled
+- **Formatting**: Prettier defaults
+- **Package Manager**: Bun (never use npm/yarn)
-Set `RecognitionOptions.DebugOutputDirectory` to save pipeline stages:
-- `01_input.png` - Original image
-- `02_detection.png` - Card detection visualization
-- `03_perspective_corrected.png` - Warped card
-- `05_clahe_*.png` - After CLAHE preprocessing
+### React/React Native
-On Android: `/sdcard/Download/scry-debug` (pull with `adb pull`)
+- Functional components with hooks
+- Expo Router for navigation (file-based)
+- Convex hooks for data (`useQuery`, `useMutation`)
-## Dependencies
+### Naming
-### Core Library (Scry.Core)
+- Hooks: `useCardHashes`, `useCameraPermission`
+- Components: PascalCase (`AdaptiveCamera`, `ScanScreen`)
+- Files: camelCase for modules, PascalCase for components
+- Convex functions: camelCase (`cards.ts`, `getByScryfallId`)
-- **SkiaSharp** - Image processing, DCT, edge detection
-- **Microsoft.Data.Sqlite** - SQLite database
-- **Microsoft.Extensions.Options** - Options pattern
+### Convex Backend
-### App (Scry.App)
+- Queries are reactive and cached
+- Mutations are transactional
+- Use `v.` validators for all arguments
+- Index frequently queried fields
-- **CommunityToolkit.Maui** - MAUI extensions
-- **CommunityToolkit.Maui.Camera** - Camera integration
-- **CommunityToolkit.Mvvm** - MVVM source generators
+## Environment Variables
-### DbGenerator Tool
+### Convex Backend (`convex/.env.local`)
-- **Spectre.Console** / **Spectre.Console.Cli** - Rich terminal UI
+```bash
+AUTH_ZITADEL_ID=your-client-id
+AUTH_ZITADEL_SECRET=your-client-secret
+AUTH_ZITADEL_ISSUER=https://your-zitadel-instance
+```
+
+### Client Side (`.env`)
+
+```bash
+EXPO_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
+EXPO_PUBLIC_ZITADEL_ISSUER=https://your-zitadel-instance
+```
## Common Tasks
-### Adding a New Card to Priority Test Set
+### Adding a New Convex Function
-1. Add image to `TestImages/reference_alpha/` or appropriate folder
-2. Add entry to `GenerateCommand.PriorityCardsWithSets` dictionary
-3. Run `just gen-db` to regenerate database
+1. Add function to appropriate file in `convex/` (e.g., `cards.ts`)
+2. Run `bunx convex dev` to regenerate types
+3. Import from `convex/_generated/api` in client code
-### Debugging Recognition Failures
+### Testing Recognition
-1. Enable debug output in `MauiProgram.cs`:
- ```csharp
- options.DebugOutputDirectory = "/sdcard/Download/scry-debug";
- ```
-2. Run recognition
-3. Pull debug images: `adb pull /sdcard/Download/scry-debug`
-4. Compare `05_clahe_*.png` with reference images in database
+1. Add test images to `TestImages/`
+2. Use the scan tab in the app
+3. Check console logs for `[Scry]` prefixed messages
-### Modifying Hash Algorithm
+### Debugging Camera Issues
-1. Update `PerceptualHash.ComputeColorHash()`
-2. Update `CardRecognitionService.ColorHashBits` constant
-3. Regenerate database: `just gen-db --force`
-4. Run tests: `just test`
+- In Expo Go: Uses `expo-camera`, check for "Dev mode" indicator
+- In production: Uses Vision Camera, requires EAS build
-## Gotchas
+## Dependencies
-1. **Hash size is 24 bytes (192 bits)** - 3 RGB channels × 8 bytes each
-2. **Confidence threshold is 85%** - Configurable in `CardRecognitionService.MinConfidence`
-3. **Card detection is optional** - Controlled by `RecognitionOptions.EnableCardDetection`
-4. **Rotation matching tries 4 orientations** - Controlled by `RecognitionOptions.EnableRotationMatching`
-5. **Database is bundled in APK** - Copied on first run to app data directory
-6. **Multi-face cards** - Only front face image is used for hashing
-7. **Rate limiting** - DbGenerator uses 50ms delay between Scryfall image downloads
+### Core
-## CI/CD
+- `expo` ~54.0.33
+- `expo-router` ~6.0.23
+- `convex` ^1.31.7
+- `@convex-dev/auth` ^0.0.90
+- `react` 19.1.0
+- `react-native` 0.81.5
-Forgejo Actions workflow (`.forgejo/workflows/release.yml`):
-- Builds for win-x64, linux-x64, osx-x64
-- Creates "standard" and "embedded" (with APK) variants
-- Publishes to Forgejo releases
+### Image Processing
+
+- `@shopify/react-native-skia` ^2.4.18
+- `react-native-fast-opencv` ^0.4.7
+
+### Camera
+
+- `expo-camera` ^17.0.10 (Expo Go)
+- `react-native-vision-camera` ^4.7.3 (production)
+
+### Auth
+
+- `expo-auth-session` ^7.0.10
+- `expo-secure-store` ^15.0.8
## External Resources
- [Scryfall API](https://scryfall.com/docs/api) - Card data source
-- [CARD_RECOGNITION.md](docs/CARD_RECOGNITION.md) - Detailed architecture doc
+- [Convex Docs](https://docs.convex.dev/) - Backend documentation
+- [Expo Router](https://docs.expo.dev/router/introduction/) - Navigation
+- [docs/CARD_RECOGNITION.md](docs/CARD_RECOGNITION.md) - Recognition architecture
diff --git a/Directory.Build.props b/Directory.Build.props
deleted file mode 100644
index b9cdb07..0000000
--- a/Directory.Build.props
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- net10.0
- enable
- enable
- true
- latest
- true
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
deleted file mode 100644
index baa520b..0000000
--- a/Directory.Packages.props
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Scry.slnx b/Scry.slnx
deleted file mode 100644
index 8a2e845..0000000
--- a/Scry.slnx
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app.json b/app.json
new file mode 100644
index 0000000..f673b46
--- /dev/null
+++ b/app.json
@@ -0,0 +1,39 @@
+{
+ "expo": {
+ "name": "Scry",
+ "slug": "scry",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/images/icon.png",
+ "scheme": "scry",
+ "userInterfaceStyle": "automatic",
+ "newArchEnabled": true,
+ "splash": {
+ "image": "./assets/images/splash-icon.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/images/adaptive-icon.png",
+ "backgroundColor": "#ffffff"
+ },
+ "edgeToEdgeEnabled": true,
+ "predictiveBackGestureEnabled": false
+ },
+ "web": {
+ "bundler": "metro",
+ "output": "static",
+ "favicon": "./assets/images/favicon.png"
+ },
+ "plugins": [
+ "expo-router"
+ ],
+ "experiments": {
+ "typedRoutes": true
+ }
+ }
+}
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
new file mode 100644
index 0000000..8454c7d
--- /dev/null
+++ b/app/(tabs)/_layout.tsx
@@ -0,0 +1,58 @@
+import React from "react";
+import FontAwesome from "@expo/vector-icons/FontAwesome";
+import { Tabs } from "expo-router";
+import { useColorScheme } from "@/components/useColorScheme";
+import Colors from "@/constants/Colors";
+
+function TabBarIcon(props: {
+ name: React.ComponentProps["name"];
+ color: string;
+}) {
+ return ;
+}
+
+export default function TabLayout() {
+ const colorScheme = useColorScheme();
+
+ return (
+
+ (
+
+ ),
+ }}
+ />
+ ,
+ }}
+ />
+ ,
+ }}
+ />
+
+ );
+}
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
new file mode 100644
index 0000000..3219241
--- /dev/null
+++ b/app/(tabs)/index.tsx
@@ -0,0 +1,406 @@
+/**
+ * Collection screen - displays user's scanned cards.
+ */
+
+import React, { useState, useCallback, useMemo } from "react";
+import {
+ StyleSheet,
+ View,
+ Text,
+ FlatList,
+ Image,
+ Pressable,
+ TextInput,
+ RefreshControl,
+ Dimensions,
+ ActivityIndicator,
+} from "react-native";
+import { FontAwesome } from "@expo/vector-icons";
+import { useQuery } from "convex/react";
+import { useRouter } from "expo-router";
+import { api } from "../../convex/_generated/api";
+import { useCurrentUser } from "@/lib/hooks";
+
+const { width: SCREEN_WIDTH } = Dimensions.get("window");
+const CARD_WIDTH = (SCREEN_WIDTH - 48) / 3;
+const CARD_HEIGHT = CARD_WIDTH * (88 / 63); // MTG aspect ratio
+
+interface CollectionItem {
+ id: string;
+ cardId: string;
+ name: string;
+ setCode: string;
+ imageUri?: string;
+ quantity: number;
+ isFoil: boolean;
+ addedAt: number;
+}
+
+export default function CollectionScreen() {
+ const router = useRouter();
+ const [searchQuery, setSearchQuery] = useState("");
+ const [refreshing, setRefreshing] = useState(false);
+ const [sortBy, setSortBy] = useState<"name" | "set" | "recent">("recent");
+
+ // Get authenticated user
+ const { user, isAuthenticated } = useCurrentUser();
+ const userId = user?._id ?? null;
+
+ // Fetch collection from Convex
+ const rawCollection = useQuery(
+ api.collections.getByUser,
+ userId ? { userId: userId as any } : "skip"
+ );
+
+ // Transform Convex data to UI format
+ const collection = useMemo(() => {
+ if (!rawCollection) return [];
+
+ return rawCollection.map((entry) => ({
+ id: entry._id,
+ cardId: entry.cardId,
+ name: entry.card?.name || "Unknown",
+ setCode: entry.card?.setCode || "???",
+ imageUri: entry.card?.imageUri,
+ quantity: entry.quantity,
+ isFoil: entry.isFoil,
+ addedAt: entry.addedAt,
+ }));
+ }, [rawCollection]);
+
+ // Filter collection by search query
+ const filteredCollection = useMemo(
+ () =>
+ collection.filter((item) =>
+ item.name.toLowerCase().includes(searchQuery.toLowerCase())
+ ),
+ [collection, searchQuery]
+ );
+
+ // Sort collection
+ const sortedCollection = useMemo(
+ () =>
+ [...filteredCollection].sort((a, b) => {
+ switch (sortBy) {
+ case "name":
+ return a.name.localeCompare(b.name);
+ case "set":
+ return a.setCode.localeCompare(b.setCode);
+ case "recent":
+ default:
+ return b.addedAt - a.addedAt;
+ }
+ }),
+ [filteredCollection, sortBy]
+ );
+
+ const onRefresh = useCallback(async () => {
+ setRefreshing(true);
+ // Convex automatically syncs - just show loading briefly
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ setRefreshing(false);
+ }, []);
+
+ const renderCard = useCallback(
+ ({ item }: { item: CollectionItem }) => (
+
+ router.push({
+ pathname: "/modal",
+ params: {
+ collectionEntryId: item.id,
+ cardId: item.cardId,
+ },
+ })
+ }
+ >
+ {item.imageUri ? (
+
+ ) : (
+
+
+
+ )}
+ {item.quantity > 1 && (
+
+ ×{item.quantity}
+
+ )}
+ {item.isFoil && (
+
+
+
+ )}
+
+ ),
+ [router]
+ );
+
+ const renderEmpty = useCallback(
+ () => (
+
+
+ No Cards Yet
+
+ {userId
+ ? "Start scanning cards to build your collection!"
+ : "Sign in to start building your collection!"}
+
+
+ ),
+ [userId]
+ );
+
+ // Loading state
+ if (rawCollection === undefined && userId) {
+ return (
+
+
+ Loading collection...
+
+ );
+ }
+
+ // Total cards count
+ const totalCount = collection.reduce((sum, item) => sum + item.quantity, 0);
+
+ return (
+
+ {/* Search bar */}
+
+
+
+ {searchQuery.length > 0 && (
+ setSearchQuery("")}>
+
+
+ )}
+
+
+ {/* Stats and sort bar */}
+
+
+ {sortedCollection.length} unique
+ {collection.length > 0 && ` (${totalCount} total)`}
+
+
+
+ setSortBy("recent")}
+ >
+
+ Recent
+
+
+ setSortBy("name")}
+ >
+
+ Name
+
+
+ setSortBy("set")}
+ >
+
+ Set
+
+
+
+
+
+ {/* Card grid */}
+ item.id}
+ numColumns={3}
+ contentContainerStyle={styles.gridContent}
+ ListEmptyComponent={renderEmpty}
+ refreshControl={
+
+ }
+ />
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#1a1a1a",
+ },
+ searchContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ backgroundColor: "#2a2a2a",
+ marginHorizontal: 16,
+ marginTop: 16,
+ marginBottom: 8,
+ paddingHorizontal: 12,
+ borderRadius: 10,
+ },
+ searchIcon: {
+ marginRight: 8,
+ },
+ searchInput: {
+ flex: 1,
+ height: 44,
+ color: "#fff",
+ fontSize: 16,
+ },
+ statsBar: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ },
+ statsText: {
+ color: "#888",
+ fontSize: 14,
+ },
+ sortButtons: {
+ flexDirection: "row",
+ gap: 4,
+ },
+ sortButton: {
+ paddingHorizontal: 10,
+ paddingVertical: 6,
+ borderRadius: 6,
+ },
+ sortButtonActive: {
+ backgroundColor: "#333",
+ },
+ sortButtonText: {
+ color: "#666",
+ fontSize: 12,
+ fontWeight: "500",
+ },
+ sortButtonTextActive: {
+ color: "#fff",
+ },
+ gridContent: {
+ padding: 16,
+ paddingTop: 8,
+ },
+ cardContainer: {
+ width: CARD_WIDTH,
+ height: CARD_HEIGHT,
+ marginRight: 8,
+ marginBottom: 8,
+ borderRadius: 8,
+ overflow: "hidden",
+ },
+ cardImage: {
+ width: "100%",
+ height: "100%",
+ backgroundColor: "#2a2a2a",
+ borderRadius: 8,
+ },
+ cardPlaceholder: {
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ quantityBadge: {
+ position: "absolute",
+ top: 4,
+ right: 4,
+ backgroundColor: "rgba(0,0,0,0.8)",
+ paddingHorizontal: 6,
+ paddingVertical: 2,
+ borderRadius: 4,
+ },
+ quantityText: {
+ color: "#fff",
+ fontSize: 11,
+ fontWeight: "600",
+ },
+ foilBadge: {
+ position: "absolute",
+ top: 4,
+ left: 4,
+ backgroundColor: "rgba(0,0,0,0.8)",
+ padding: 4,
+ borderRadius: 4,
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ paddingTop: 100,
+ paddingHorizontal: 40,
+ },
+ emptyTitle: {
+ marginTop: 20,
+ fontSize: 22,
+ fontWeight: "bold",
+ color: "#fff",
+ },
+ emptyText: {
+ marginTop: 8,
+ fontSize: 16,
+ color: "#888",
+ textAlign: "center",
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: "#1a1a1a",
+ },
+ loadingText: {
+ marginTop: 16,
+ color: "#888",
+ fontSize: 16,
+ },
+});
diff --git a/app/(tabs)/scan.tsx b/app/(tabs)/scan.tsx
new file mode 100644
index 0000000..9ad3d12
--- /dev/null
+++ b/app/(tabs)/scan.tsx
@@ -0,0 +1,572 @@
+/**
+ * Camera scanning screen for card recognition.
+ */
+
+import React, { useCallback, useRef, useState, useEffect } from "react";
+import {
+ StyleSheet,
+ View,
+ Text,
+ Pressable,
+ ActivityIndicator,
+ Dimensions,
+ Image,
+ Alert,
+} from "react-native";
+import { FontAwesome } from "@expo/vector-icons";
+import { useMutation, useQuery } from "convex/react";
+import { api } from "../../convex/_generated/api";
+import { useCameraPermission } from "@/lib/hooks/useCamera";
+import { useCurrentUser } from "@/lib/hooks";
+import { useHashCache } from "@/lib/context";
+import { recognizeCard } from "@/lib/recognition";
+import { loadImageAsBase64 } from "@/lib/recognition/imageLoader";
+import { decodeImageBase64 } from "@/lib/recognition/skiaDecoder";
+import { AdaptiveCamera, CameraHandle, isExpoGo } from "@/components/camera";
+
+const { height: SCREEN_HEIGHT } = Dimensions.get("window");
+const CARD_ASPECT_RATIO = 63 / 88; // Width / Height
+const SCAN_BOX_HEIGHT = SCREEN_HEIGHT * 0.5;
+const SCAN_BOX_WIDTH = SCAN_BOX_HEIGHT * CARD_ASPECT_RATIO;
+
+interface ScanResult {
+ cardId: string;
+ cardName: string;
+ setCode: string;
+ imageUri?: string;
+ confidence: number;
+ distance: number;
+ timestamp: number;
+}
+
+export default function ScanScreen() {
+ const { hasPermission, isLoading: permLoading, requestPermission } = useCameraPermission();
+ const cameraRef = useRef(null);
+
+ // Local scan state
+ const [isProcessing, setIsProcessing] = useState(false);
+ const [flashEnabled, setFlashEnabled] = useState(false);
+ const [isAddingToCollection, setIsAddingToCollection] = useState(false);
+ const [lastScanResult, setLastScanResult] = useState(null);
+ const [scanError, setScanError] = useState(null);
+
+ // Hash cache from context
+ const { cardHashes, hashesLoaded } = useHashCache();
+
+ // Get authenticated user
+ const { user, isAuthenticated } = useCurrentUser();
+ const userId = user?._id ?? null;
+
+ // Convex mutations
+ const addToCollection = useMutation(api.collections.add);
+ const getCardByScryfallId = useQuery(
+ api.cards.getByScryfallId,
+ lastScanResult ? { scryfallId: lastScanResult.cardId } : "skip"
+ );
+
+ const clearScanState = useCallback(() => {
+ setLastScanResult(null);
+ setScanError(null);
+ }, []);
+
+ // Auto-clear scan result after 5 seconds
+ useEffect(() => {
+ if (lastScanResult) {
+ const timer = setTimeout(() => {
+ clearScanState();
+ }, 5000);
+ return () => clearTimeout(timer);
+ }
+ }, [lastScanResult, clearScanState]);
+
+ const handleCapture = useCallback(async () => {
+ if (!cameraRef.current || isProcessing || !hashesLoaded) return;
+
+ setIsProcessing(true);
+
+ try {
+ // Take photo using adaptive camera
+ const photo = await cameraRef.current.takePhoto();
+
+ console.log("[Scry] Photo captured:", photo.uri);
+
+ // Load and resize image for processing
+ const { base64 } = await loadImageAsBase64(
+ photo.uri,
+ 480, // Target width for processing
+ 640 // Target height
+ );
+
+ if (!base64) {
+ throw new Error("Failed to load image data");
+ }
+
+ // Decode to RGBA pixels using Skia
+ const decoded = decodeImageBase64(base64);
+
+ if (!decoded) {
+ throw new Error("Failed to decode image pixels");
+ }
+
+ console.log("[Scry] Image decoded:", decoded.width, "x", decoded.height);
+ console.log("[Scry] Matching against", cardHashes.length, "cards");
+
+ // Run recognition
+ const result = recognizeCard(
+ decoded.pixels,
+ decoded.width,
+ decoded.height,
+ cardHashes,
+ {
+ enableCardDetection: true,
+ enableRotationMatching: true,
+ minConfidence: 0.85,
+ }
+ );
+
+ console.log("[Scry] Recognition result:", result);
+
+ if (result.success && result.match) {
+ // Find the card info from our hashes
+ const matchedCard = cardHashes.find((c) => c.id === result.match!.cardId);
+
+ setLastScanResult({
+ cardId: result.match.cardId,
+ cardName: matchedCard?.name || "Unknown Card",
+ setCode: matchedCard?.setCode || "???",
+ imageUri: matchedCard?.imageUri,
+ confidence: result.match.confidence,
+ distance: result.match.distance,
+ timestamp: Date.now(),
+ });
+ setScanError(null);
+ } else {
+ setScanError(result.error || "No match found. Try adjusting lighting or angle.");
+ setLastScanResult(null);
+ }
+ } catch (error) {
+ console.error("[Scry] Capture error:", error);
+ setScanError(error instanceof Error ? error.message : "Failed to capture image");
+ setLastScanResult(null);
+ } finally {
+ setIsProcessing(false);
+ }
+ }, [isProcessing, hashesLoaded, cardHashes]);
+
+ const handleAddToCollection = useCallback(async () => {
+ if (!lastScanResult || !userId || !getCardByScryfallId) return;
+
+ setIsAddingToCollection(true);
+
+ try {
+ await addToCollection({
+ userId: userId as any,
+ cardId: getCardByScryfallId._id,
+ quantity: 1,
+ isFoil: false,
+ });
+
+ Alert.alert(
+ "Added to Collection",
+ `${lastScanResult.cardName} has been added to your collection.`,
+ [{ text: "OK" }]
+ );
+
+ clearScanState();
+ } catch (error) {
+ console.error("[Scry] Failed to add to collection:", error);
+ Alert.alert("Error", "Failed to add card to collection. Please try again.", [
+ { text: "OK" },
+ ]);
+ } finally {
+ setIsAddingToCollection(false);
+ }
+ }, [lastScanResult, userId, getCardByScryfallId, addToCollection, clearScanState]);
+
+ // Loading state
+ if (permLoading) {
+ return (
+
+
+ Checking camera permission...
+
+ );
+ }
+
+ // Permission denied
+ if (!hasPermission) {
+ return (
+
+
+ Camera Access Required
+
+ Scry needs camera access to scan your Magic cards.
+
+
+ Enable Camera
+
+
+ );
+ }
+
+ return (
+
+ {/* Adaptive camera - uses expo-camera in Expo Go, Vision Camera in production */}
+
+
+ {/* Overlay with scan box */}
+
+ {/* Top dark area */}
+
+
+ {/* Middle row with scan box */}
+
+
+
+ {/* Corner markers */}
+
+
+
+
+
+
+
+
+ {/* Bottom dark area */}
+
+
+
+ {/* Instructions */}
+
+
+ {hashesLoaded
+ ? `Position card in frame • ${cardHashes.length} cards loaded`
+ : "Loading card database..."}
+
+ {isExpoGo && (
+ Dev mode (expo-camera)
+ )}
+
+
+ {/* Scan result overlay */}
+ {lastScanResult && (
+
+
+ {lastScanResult.imageUri && (
+
+ )}
+
+ {lastScanResult.cardName}
+ {lastScanResult.setCode.toUpperCase()}
+
+ {Math.round(lastScanResult.confidence * 100)}% match
+
+
+
+ {isAddingToCollection ? (
+
+ ) : (
+ <>
+
+ Add
+ >
+ )}
+
+
+
+ )}
+
+ {/* Error message */}
+ {scanError && (
+
+ {scanError}
+
+ Dismiss
+
+
+ )}
+
+ {/* Controls */}
+
+ {/* Flash toggle */}
+ setFlashEnabled(!flashEnabled)}>
+
+
+
+ {/* Capture button */}
+
+ {isProcessing ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Placeholder for symmetry */}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#000",
+ },
+ centered: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: "#1a1a1a",
+ padding: 20,
+ },
+ loadingText: {
+ marginTop: 16,
+ color: "#888",
+ fontSize: 16,
+ },
+ permissionTitle: {
+ marginTop: 20,
+ fontSize: 24,
+ fontWeight: "bold",
+ color: "#fff",
+ },
+ permissionText: {
+ marginTop: 12,
+ fontSize: 16,
+ color: "#888",
+ textAlign: "center",
+ maxWidth: 280,
+ },
+ permissionButton: {
+ marginTop: 24,
+ backgroundColor: "#007AFF",
+ paddingHorizontal: 32,
+ paddingVertical: 14,
+ borderRadius: 12,
+ },
+ permissionButtonText: {
+ color: "#fff",
+ fontSize: 18,
+ fontWeight: "600",
+ },
+ errorText: {
+ marginTop: 16,
+ fontSize: 18,
+ color: "#FF6B6B",
+ textAlign: "center",
+ },
+ overlay: {
+ ...StyleSheet.absoluteFillObject,
+ },
+ overlayTop: {
+ flex: 1,
+ backgroundColor: "rgba(0,0,0,0.6)",
+ },
+ overlayMiddle: {
+ flexDirection: "row",
+ height: SCAN_BOX_HEIGHT,
+ },
+ overlaySide: {
+ flex: 1,
+ backgroundColor: "rgba(0,0,0,0.6)",
+ },
+ scanBox: {
+ width: SCAN_BOX_WIDTH,
+ height: SCAN_BOX_HEIGHT,
+ borderWidth: 2,
+ borderColor: "rgba(255,255,255,0.3)",
+ borderRadius: 12,
+ },
+ overlayBottom: {
+ flex: 1,
+ backgroundColor: "rgba(0,0,0,0.6)",
+ },
+ corner: {
+ position: "absolute",
+ width: 24,
+ height: 24,
+ borderColor: "#007AFF",
+ },
+ cornerTopLeft: {
+ top: -2,
+ left: -2,
+ borderTopWidth: 4,
+ borderLeftWidth: 4,
+ borderTopLeftRadius: 12,
+ },
+ cornerTopRight: {
+ top: -2,
+ right: -2,
+ borderTopWidth: 4,
+ borderRightWidth: 4,
+ borderTopRightRadius: 12,
+ },
+ cornerBottomLeft: {
+ bottom: -2,
+ left: -2,
+ borderBottomWidth: 4,
+ borderLeftWidth: 4,
+ borderBottomLeftRadius: 12,
+ },
+ cornerBottomRight: {
+ bottom: -2,
+ right: -2,
+ borderBottomWidth: 4,
+ borderRightWidth: 4,
+ borderBottomRightRadius: 12,
+ },
+ instructionContainer: {
+ position: "absolute",
+ top: 60,
+ left: 0,
+ right: 0,
+ alignItems: "center",
+ },
+ instructionText: {
+ color: "#fff",
+ fontSize: 14,
+ backgroundColor: "rgba(0,0,0,0.5)",
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 20,
+ },
+ devModeText: {
+ marginTop: 8,
+ color: "#FFD700",
+ fontSize: 12,
+ backgroundColor: "rgba(0,0,0,0.5)",
+ paddingHorizontal: 12,
+ paddingVertical: 4,
+ borderRadius: 12,
+ },
+ resultContainer: {
+ position: "absolute",
+ bottom: 140,
+ left: 16,
+ right: 16,
+ },
+ resultCard: {
+ flexDirection: "row",
+ backgroundColor: "rgba(0,0,0,0.9)",
+ borderRadius: 12,
+ padding: 12,
+ alignItems: "center",
+ },
+ resultImage: {
+ width: 50,
+ height: 70,
+ borderRadius: 4,
+ },
+ resultInfo: {
+ flex: 1,
+ marginLeft: 12,
+ },
+ resultName: {
+ color: "#fff",
+ fontSize: 16,
+ fontWeight: "600",
+ },
+ resultSet: {
+ color: "#888",
+ fontSize: 12,
+ marginTop: 2,
+ },
+ resultConfidence: {
+ color: "#4CD964",
+ fontSize: 12,
+ marginTop: 4,
+ },
+ addButton: {
+ flexDirection: "row",
+ alignItems: "center",
+ backgroundColor: "#4CD964",
+ paddingHorizontal: 16,
+ paddingVertical: 10,
+ borderRadius: 8,
+ gap: 6,
+ },
+ addButtonDisabled: {
+ opacity: 0.5,
+ },
+ addButtonText: {
+ color: "#fff",
+ fontSize: 14,
+ fontWeight: "600",
+ },
+ errorContainer: {
+ position: "absolute",
+ bottom: 140,
+ left: 16,
+ right: 16,
+ backgroundColor: "rgba(255,107,107,0.95)",
+ borderRadius: 12,
+ padding: 16,
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ },
+ errorMessage: {
+ color: "#fff",
+ fontSize: 14,
+ flex: 1,
+ },
+ errorDismiss: {
+ color: "#fff",
+ fontSize: 14,
+ fontWeight: "600",
+ marginLeft: 12,
+ },
+ controls: {
+ position: "absolute",
+ bottom: 40,
+ left: 0,
+ right: 0,
+ flexDirection: "row",
+ justifyContent: "space-around",
+ alignItems: "center",
+ paddingHorizontal: 40,
+ },
+ controlButton: {
+ width: 50,
+ height: 50,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ captureButton: {
+ width: 72,
+ height: 72,
+ borderRadius: 36,
+ backgroundColor: "#fff",
+ justifyContent: "center",
+ alignItems: "center",
+ borderWidth: 4,
+ borderColor: "rgba(255,255,255,0.3)",
+ },
+ captureButtonDisabled: {
+ opacity: 0.5,
+ },
+ captureButtonInner: {
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ backgroundColor: "#fff",
+ },
+});
diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx
new file mode 100644
index 0000000..5fa9c83
--- /dev/null
+++ b/app/(tabs)/settings.tsx
@@ -0,0 +1,313 @@
+/**
+ * Settings screen for Scry app.
+ */
+
+import React, { useState } from "react";
+import {
+ StyleSheet,
+ View,
+ Text,
+ ScrollView,
+ Pressable,
+ Switch,
+ ActivityIndicator,
+ Alert,
+} from "react-native";
+import { FontAwesome } from "@expo/vector-icons";
+import { useQuery } from "convex/react";
+import { api } from "../../convex/_generated/api";
+import { useSync } from "@/lib/hooks/useSync";
+import { useHashCache } from "@/lib/context";
+
+interface SettingRowProps {
+ icon: React.ComponentProps["name"];
+ title: string;
+ subtitle?: string;
+ onPress?: () => void;
+ rightElement?: React.ReactNode;
+ destructive?: boolean;
+}
+
+function SettingRow({
+ icon,
+ title,
+ subtitle,
+ onPress,
+ rightElement,
+ destructive,
+}: SettingRowProps) {
+ return (
+ [
+ styles.settingRow,
+ pressed && onPress && styles.settingRowPressed,
+ ]}
+ onPress={onPress}
+ disabled={!onPress}
+ >
+
+
+
+
+
+ {title}
+
+ {subtitle && {subtitle}}
+
+ {rightElement || (onPress && )}
+
+ );
+}
+
+function SettingSection({ title, children }: { title: string; children: React.ReactNode }) {
+ return (
+
+ {title}
+ {children}
+
+ );
+}
+
+export default function SettingsScreen() {
+ const [cardDetectionEnabled, setCardDetectionEnabled] = useState(true);
+ const [rotationMatchingEnabled, setRotationMatchingEnabled] = useState(true);
+
+ // Get hash count from context
+ const { cardHashes, hashesLoaded } = useHashCache();
+
+ // Sync hook for cache management
+ const { isInitialized, isSyncing, lastSync, localCardCount, error: syncError, sync, clearCache } =
+ useSync();
+
+ // Get total card count from Convex
+ const cardCount = useQuery(api.cards.count);
+
+ const formatLastSync = (timestamp: number) => {
+ if (!timestamp) return "Never";
+ const date = new Date(timestamp);
+ return date.toLocaleDateString() + " " + date.toLocaleTimeString();
+ };
+
+ const handleClearCache = () => {
+ Alert.alert(
+ "Clear Local Cache",
+ "This will remove all downloaded card data. You'll need to sync again to scan cards.",
+ [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Clear",
+ style: "destructive",
+ onPress: async () => {
+ await clearCache();
+ Alert.alert("Cache Cleared", "Local card data has been removed.");
+ },
+ },
+ ]
+ );
+ };
+
+ const handleManualSync = async () => {
+ await sync();
+ Alert.alert("Sync Complete", `${localCardCount} cards now available for scanning.`);
+ };
+
+ return (
+
+ {/* Database section */}
+
+ : undefined}
+ />
+ : undefined}
+ />
+ {syncError && (
+
+ )}
+
+
+ {/* Recognition section */}
+
+
+ }
+ />
+
+ }
+ />
+
+
+ {/* Collection section */}
+
+
+ {
+ // TODO: Implement export
+ }}
+ />
+
+
+ {/* About section */}
+
+
+ {
+ // TODO: Open source URL
+ }}
+ />
+
+
+ {/* Danger zone */}
+
+ {
+ Alert.alert(
+ "Clear Collection",
+ "This will remove all cards from your collection. This cannot be undone.",
+ [
+ { text: "Cancel", style: "cancel" },
+ { text: "Clear", style: "destructive", onPress: () => {} },
+ ]
+ );
+ }}
+ destructive
+ />
+
+
+
+ {/* Footer */}
+
+ Scry • Card scanner for Magic: The Gathering
+ Card data © Wizards of the Coast
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#1a1a1a",
+ },
+ content: {
+ paddingBottom: 40,
+ },
+ section: {
+ marginTop: 24,
+ },
+ sectionTitle: {
+ color: "#888",
+ fontSize: 13,
+ fontWeight: "600",
+ textTransform: "uppercase",
+ letterSpacing: 0.5,
+ marginLeft: 16,
+ marginBottom: 8,
+ },
+ sectionContent: {
+ backgroundColor: "#2a2a2a",
+ borderRadius: 12,
+ marginHorizontal: 16,
+ overflow: "hidden",
+ },
+ settingRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ padding: 14,
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderBottomColor: "#3a3a3a",
+ },
+ settingRowPressed: {
+ backgroundColor: "#333",
+ },
+ settingIcon: {
+ width: 32,
+ height: 32,
+ borderRadius: 8,
+ backgroundColor: "rgba(0,122,255,0.1)",
+ justifyContent: "center",
+ alignItems: "center",
+ marginRight: 12,
+ },
+ settingIconDestructive: {
+ backgroundColor: "rgba(255,107,107,0.1)",
+ },
+ settingContent: {
+ flex: 1,
+ },
+ settingTitle: {
+ color: "#fff",
+ fontSize: 16,
+ },
+ settingTitleDestructive: {
+ color: "#FF6B6B",
+ },
+ settingSubtitle: {
+ color: "#888",
+ fontSize: 13,
+ marginTop: 2,
+ },
+ footer: {
+ marginTop: 40,
+ alignItems: "center",
+ paddingHorizontal: 16,
+ },
+ footerText: {
+ color: "#666",
+ fontSize: 14,
+ },
+ footerSubtext: {
+ color: "#444",
+ fontSize: 12,
+ marginTop: 4,
+ },
+});
diff --git a/app/+html.tsx b/app/+html.tsx
new file mode 100644
index 0000000..cb31090
--- /dev/null
+++ b/app/+html.tsx
@@ -0,0 +1,38 @@
+import { ScrollViewStyleReset } from 'expo-router/html';
+
+// This file is web-only and used to configure the root HTML for every
+// web page during static rendering.
+// The contents of this function only run in Node.js environments and
+// do not have access to the DOM or browser APIs.
+export default function Root({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+ {/*
+ Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
+ However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
+ */}
+
+
+ {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
+
+ {/* Add any additional elements that you want globally available on web... */}
+
+ {children}
+
+ );
+}
+
+const responsiveBackground = `
+body {
+ background-color: #fff;
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #000;
+ }
+}`;
diff --git a/app/+not-found.tsx b/app/+not-found.tsx
new file mode 100644
index 0000000..ffb5643
--- /dev/null
+++ b/app/+not-found.tsx
@@ -0,0 +1,40 @@
+import { Link, Stack } from 'expo-router';
+import { StyleSheet } from 'react-native';
+
+import { Text, View } from '@/components/Themed';
+
+export default function NotFoundScreen() {
+ return (
+ <>
+
+
+ This screen doesn't exist.
+
+
+ Go to home screen!
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 20,
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ },
+ link: {
+ marginTop: 15,
+ paddingVertical: 15,
+ },
+ linkText: {
+ fontSize: 14,
+ color: '#2e78b7',
+ },
+});
diff --git a/app/_layout.tsx b/app/_layout.tsx
new file mode 100644
index 0000000..f3949e4
--- /dev/null
+++ b/app/_layout.tsx
@@ -0,0 +1,72 @@
+import React, { useEffect } from "react";
+import FontAwesome from "@expo/vector-icons/FontAwesome";
+import { useFonts } from "expo-font";
+import * as SplashScreen from "expo-splash-screen";
+import { Stack } from "expo-router";
+import { ConvexProvider, ConvexReactClient } from "convex/react";
+import { HashCacheProvider } from "@/lib/context";
+import { useColorScheme } from "@/components/useColorScheme";
+
+// Initialize Convex client
+const convex = new ConvexReactClient(
+ process.env.EXPO_PUBLIC_CONVEX_URL as string
+);
+
+// Keep splash screen visible while loading fonts
+SplashScreen.preventAutoHideAsync();
+
+export default function RootLayout() {
+ const [loaded, error] = useFonts({
+ SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
+ ...FontAwesome.font,
+ });
+
+ useEffect(() => {
+ if (error) throw error;
+ }, [error]);
+
+ useEffect(() => {
+ if (loaded) {
+ SplashScreen.hideAsync();
+ }
+ }, [loaded]);
+
+ if (!loaded) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+
+function RootLayoutNav() {
+ const colorScheme = useColorScheme();
+
+ return (
+
+
+
+
+ );
+}
diff --git a/app/modal.tsx b/app/modal.tsx
new file mode 100644
index 0000000..3415938
--- /dev/null
+++ b/app/modal.tsx
@@ -0,0 +1,376 @@
+/**
+ * Card detail modal - shows full card info with quantity controls.
+ */
+
+import React, { useState } from "react";
+import {
+ StyleSheet,
+ View,
+ Text,
+ Image,
+ Pressable,
+ ScrollView,
+ ActivityIndicator,
+ Alert,
+ Dimensions,
+ Platform,
+} from "react-native";
+import { StatusBar } from "expo-status-bar";
+import { useLocalSearchParams, useRouter } from "expo-router";
+import { FontAwesome } from "@expo/vector-icons";
+import { useQuery, useMutation } from "convex/react";
+import { api } from "../convex/_generated/api";
+import { useCurrentUser } from "@/lib/hooks";
+
+const { width: SCREEN_WIDTH } = Dimensions.get("window");
+const CARD_WIDTH = SCREEN_WIDTH - 64;
+const CARD_HEIGHT = CARD_WIDTH * (88 / 63);
+
+export default function CardDetailModal() {
+ const router = useRouter();
+ const params = useLocalSearchParams<{
+ collectionEntryId: string;
+ cardId: string;
+ }>();
+
+ // Get authenticated user
+ const { user } = useCurrentUser();
+ const userId = user?._id ?? null;
+
+ const [isUpdating, setIsUpdating] = useState(false);
+
+ // Fetch card details
+ const card = useQuery(
+ api.cards.getByScryfallId,
+ params.cardId ? { scryfallId: params.cardId } : "skip"
+ );
+
+ // Fetch collection entry
+ const collection = useQuery(
+ api.collections.getByUser,
+ userId ? { userId: userId as any } : "skip"
+ );
+
+ const collectionEntry = collection?.find(
+ (entry) => entry._id === params.collectionEntryId
+ );
+
+ // Mutations
+ const updateQuantity = useMutation(api.collections.updateQuantity);
+ const removeCard = useMutation(api.collections.remove);
+
+ const handleQuantityChange = async (delta: number) => {
+ if (!collectionEntry) return;
+
+ const newQuantity = collectionEntry.quantity + delta;
+
+ if (newQuantity <= 0) {
+ handleRemove();
+ return;
+ }
+
+ setIsUpdating(true);
+ try {
+ await updateQuantity({
+ entryId: collectionEntry._id,
+ quantity: newQuantity,
+ });
+ } catch (error) {
+ console.error("Failed to update quantity:", error);
+ Alert.alert("Error", "Failed to update quantity");
+ } finally {
+ setIsUpdating(false);
+ }
+ };
+
+ const handleRemove = () => {
+ Alert.alert(
+ "Remove Card",
+ `Remove ${card?.name || "this card"} from your collection?`,
+ [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Remove",
+ style: "destructive",
+ onPress: async () => {
+ if (!collectionEntry) return;
+
+ setIsUpdating(true);
+ try {
+ await removeCard({ entryId: collectionEntry._id });
+ router.back();
+ } catch (error) {
+ console.error("Failed to remove card:", error);
+ Alert.alert("Error", "Failed to remove card");
+ setIsUpdating(false);
+ }
+ },
+ },
+ ]
+ );
+ };
+
+ // Loading state
+ if (!card || !collectionEntry) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Card image */}
+
+ {card.imageUri ? (
+
+ ) : (
+
+
+
+ )}
+
+
+ {/* Card info */}
+
+ {card.name}
+
+
+
+ Set
+ {card.setCode.toUpperCase()}
+
+
+ Number
+ {card.collectorNumber}
+
+
+ Rarity
+ {card.rarity}
+
+
+
+ {card.artist && (
+
+
+ {card.artist}
+
+ )}
+
+
+ {/* Quantity controls */}
+
+ Quantity
+
+
+ handleQuantityChange(-1)}
+ disabled={isUpdating}
+ >
+
+
+
+
+ {isUpdating ? (
+
+ ) : (
+ {collectionEntry.quantity}
+ )}
+
+
+ handleQuantityChange(1)}
+ disabled={isUpdating}
+ >
+
+
+
+
+ {collectionEntry.isFoil && (
+
+
+ Foil
+
+ )}
+
+
+ {/* Added date */}
+
+ Added {new Date(collectionEntry.addedAt).toLocaleDateString()}
+
+
+ {/* Actions */}
+
+
+
+ Remove from Collection
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#1a1a1a",
+ },
+ content: {
+ paddingBottom: 40,
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ backgroundColor: "#1a1a1a",
+ },
+ imageContainer: {
+ alignItems: "center",
+ paddingTop: 20,
+ paddingBottom: 24,
+ },
+ cardImage: {
+ width: CARD_WIDTH,
+ height: CARD_HEIGHT,
+ borderRadius: 12,
+ backgroundColor: "#2a2a2a",
+ },
+ cardPlaceholder: {
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ infoContainer: {
+ paddingHorizontal: 24,
+ marginBottom: 24,
+ },
+ cardName: {
+ color: "#fff",
+ fontSize: 24,
+ fontWeight: "bold",
+ textAlign: "center",
+ marginBottom: 16,
+ },
+ metaRow: {
+ flexDirection: "row",
+ justifyContent: "center",
+ gap: 24,
+ },
+ metaItem: {
+ alignItems: "center",
+ },
+ metaLabel: {
+ color: "#666",
+ fontSize: 12,
+ textTransform: "uppercase",
+ marginBottom: 4,
+ },
+ metaValue: {
+ color: "#fff",
+ fontSize: 16,
+ fontWeight: "600",
+ },
+ artistRow: {
+ flexDirection: "row",
+ justifyContent: "center",
+ alignItems: "center",
+ gap: 8,
+ marginTop: 16,
+ },
+ artistText: {
+ color: "#888",
+ fontSize: 14,
+ fontStyle: "italic",
+ },
+ quantityContainer: {
+ backgroundColor: "#2a2a2a",
+ marginHorizontal: 24,
+ padding: 20,
+ borderRadius: 12,
+ alignItems: "center",
+ marginBottom: 16,
+ },
+ quantityLabel: {
+ color: "#888",
+ fontSize: 12,
+ textTransform: "uppercase",
+ marginBottom: 12,
+ },
+ quantityControls: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: 20,
+ },
+ quantityButton: {
+ width: 44,
+ height: 44,
+ borderRadius: 22,
+ backgroundColor: "#007AFF",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ quantityDisplay: {
+ width: 60,
+ alignItems: "center",
+ },
+ quantityValue: {
+ color: "#fff",
+ fontSize: 28,
+ fontWeight: "bold",
+ },
+ foilBadge: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: 6,
+ marginTop: 12,
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ backgroundColor: "rgba(255,215,0,0.1)",
+ borderRadius: 6,
+ },
+ foilText: {
+ color: "#FFD700",
+ fontSize: 12,
+ fontWeight: "600",
+ },
+ addedDate: {
+ color: "#666",
+ fontSize: 12,
+ textAlign: "center",
+ marginBottom: 24,
+ },
+ actions: {
+ paddingHorizontal: 24,
+ },
+ actionButton: {
+ flexDirection: "row",
+ justifyContent: "center",
+ alignItems: "center",
+ gap: 8,
+ padding: 16,
+ borderRadius: 12,
+ },
+ removeButton: {
+ backgroundColor: "rgba(255,107,107,0.1)",
+ borderWidth: 1,
+ borderColor: "rgba(255,107,107,0.3)",
+ },
+ removeButtonText: {
+ color: "#FF6B6B",
+ fontSize: 16,
+ fontWeight: "600",
+ },
+});
diff --git a/app/two.tsx b/app/two.tsx
new file mode 100644
index 0000000..f2ea47e
--- /dev/null
+++ b/app/two.tsx
@@ -0,0 +1,31 @@
+import { StyleSheet } from 'react-native';
+
+import EditScreenInfo from '@/components/EditScreenInfo';
+import { Text, View } from '@/components/Themed';
+
+export default function TabTwoScreen() {
+ return (
+
+ Tab Two
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ },
+ separator: {
+ marginVertical: 30,
+ height: 1,
+ width: '80%',
+ },
+});
diff --git a/assets/fonts/SpaceMono-Regular.ttf b/assets/fonts/SpaceMono-Regular.ttf
new file mode 100644
index 0000000..28d7ff7
Binary files /dev/null and b/assets/fonts/SpaceMono-Regular.ttf differ
diff --git a/assets/images/adaptive-icon.png b/assets/images/adaptive-icon.png
new file mode 100644
index 0000000..03d6f6b
Binary files /dev/null and b/assets/images/adaptive-icon.png differ
diff --git a/assets/images/favicon.png b/assets/images/favicon.png
new file mode 100644
index 0000000..e75f697
Binary files /dev/null and b/assets/images/favicon.png differ
diff --git a/assets/images/icon.png b/assets/images/icon.png
new file mode 100644
index 0000000..a0b1526
Binary files /dev/null and b/assets/images/icon.png differ
diff --git a/assets/images/splash-icon.png b/assets/images/splash-icon.png
new file mode 100644
index 0000000..03d6f6b
Binary files /dev/null and b/assets/images/splash-icon.png differ
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 0000000..5950bb4
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,1737 @@
+{
+ "lockfileVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "app",
+ "dependencies": {
+ "@expo/vector-icons": "^15.0.3",
+ "@react-navigation/native": "^7.1.8",
+ "@shopify/react-native-skia": "^2.4.18",
+ "convex": "^1.31.7",
+ "expo": "~54.0.33",
+ "expo-camera": "^17.0.10",
+ "expo-constants": "~18.0.13",
+ "expo-font": "~14.0.11",
+ "expo-image-manipulator": "^14.0.8",
+ "expo-linking": "~8.0.11",
+ "expo-router": "~6.0.23",
+ "expo-splash-screen": "~31.0.13",
+ "expo-sqlite": "^16.0.10",
+ "expo-status-bar": "~3.0.9",
+ "expo-web-browser": "~15.0.10",
+ "react": "19.1.0",
+ "react-dom": "19.1.0",
+ "react-native": "0.81.5",
+ "react-native-fast-opencv": "^0.4.7",
+ "react-native-reanimated": "~4.1.1",
+ "react-native-safe-area-context": "~5.6.0",
+ "react-native-screens": "~4.16.0",
+ "react-native-vision-camera": "^4.7.3",
+ "react-native-web": "~0.21.0",
+ "react-native-worklets": "0.5.1",
+ "react-native-worklets-core": "^1.6.2",
+ "zustand": "^5.0.11",
+ },
+ "devDependencies": {
+ "@types/better-sqlite3": "^7.6.12",
+ "@types/node": "^22.10.0",
+ "@types/react": "~19.1.0",
+ "better-sqlite3": "^11.6.0",
+ "concurrently": "^9.1.0",
+ "dotenv": "^16.4.7",
+ "react-test-renderer": "19.1.0",
+ "tsx": "^4.19.2",
+ "typescript": "~5.9.2",
+ },
+ },
+ },
+ "packages": {
+ "@0no-co/graphql.web": ["@0no-co/graphql.web@1.2.0", "", { "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "optionalPeers": ["graphql"] }, "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw=="],
+
+ "@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
+
+ "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
+
+ "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@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" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
+
+ "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
+
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
+
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
+
+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="],
+
+ "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
+
+ "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="],
+
+ "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
+
+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
+
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
+
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
+
+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
+
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
+
+ "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="],
+
+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="],
+
+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
+
+ "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="],
+
+ "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
+
+ "@babel/highlight": ["@babel/highlight@7.25.9", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw=="],
+
+ "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
+
+ "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
+
+ "@babel/plugin-proposal-export-default-from": ["@babel/plugin-proposal-export-default-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw=="],
+
+ "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
+
+ "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
+
+ "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
+
+ "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
+
+ "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA=="],
+
+ "@babel/plugin-syntax-dynamic-import": ["@babel/plugin-syntax-dynamic-import@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ=="],
+
+ "@babel/plugin-syntax-export-default-from": ["@babel/plugin-syntax-export-default-from@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ=="],
+
+ "@babel/plugin-syntax-flow": ["@babel/plugin-syntax-flow@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew=="],
+
+ "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="],
+
+ "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
+
+ "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
+
+ "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
+
+ "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
+
+ "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
+
+ "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
+
+ "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
+
+ "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
+
+ "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
+
+ "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
+
+ "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
+
+ "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
+
+ "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
+
+ "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w=="],
+
+ "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="],
+
+ "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw=="],
+
+ "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw=="],
+
+ "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ=="],
+
+ "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-replace-supers": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q=="],
+
+ "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ=="],
+
+ "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="],
+
+ "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="],
+
+ "@babel/plugin-transform-flow-strip-types": ["@babel/plugin-transform-flow-strip-types@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-flow": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg=="],
+
+ "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="],
+
+ "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="],
+
+ "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="],
+
+ "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A=="],
+
+ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
+
+ "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ=="],
+
+ "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg=="],
+
+ "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w=="],
+
+ "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA=="],
+
+ "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ=="],
+
+ "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w=="],
+
+ "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="],
+
+ "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg=="],
+
+ "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA=="],
+
+ "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="],
+
+ "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="],
+
+ "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="],
+
+ "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
+
+ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
+
+ "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="],
+
+ "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog=="],
+
+ "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.29.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w=="],
+
+ "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="],
+
+ "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA=="],
+
+ "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="],
+
+ "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="],
+
+ "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
+
+ "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="],
+
+ "@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="],
+
+ "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
+
+ "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
+
+ "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
+
+ "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
+
+ "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
+
+ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.0", "", { "os": "android", "cpu": "arm" }, "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.0", "", { "os": "android", "cpu": "x64" }, "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.0", "", { "os": "none", "cpu": "x64" }, "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="],
+
+ "@expo/cli": ["@expo/cli@54.0.23", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~12.0.13", "@expo/config-plugins": "~54.0.4", "@expo/devcert": "^1.2.1", "@expo/env": "~2.0.8", "@expo/image-utils": "^0.8.8", "@expo/json-file": "^10.0.8", "@expo/metro": "~54.2.0", "@expo/metro-config": "~54.0.14", "@expo/osascript": "^2.3.8", "@expo/package-manager": "^1.9.10", "@expo/plist": "^0.4.8", "@expo/prebuild-config": "^54.0.8", "@expo/schema-utils": "^0.1.8", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.3.0", "@react-native/dev-middleware": "0.81.5", "@urql/core": "^5.0.6", "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "env-editor": "^0.4.1", "expo-server": "^1.0.5", "freeport-async": "^2.0.0", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.1.6", "minimatch": "^9.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^3.0.1", "pretty-bytes": "^5.6.0", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", "requireg": "^0.2.2", "resolve": "^1.22.2", "resolve-from": "^5.0.0", "resolve.exports": "^2.0.3", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^7.5.2", "terminal-link": "^2.1.1", "undici": "^6.18.2", "wrap-ansi": "^7.0.0", "ws": "^8.12.1" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "bin": { "expo-internal": "build/bin/cli" } }, "sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g=="],
+
+ "@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
+
+ "@expo/config": ["@expo/config@12.0.13", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~54.0.4", "@expo/config-types": "^54.0.10", "@expo/json-file": "^10.0.8", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "~3.35.1" } }, "sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ=="],
+
+ "@expo/config-plugins": ["@expo/config-plugins@54.0.4", "", { "dependencies": { "@expo/config-types": "^54.0.10", "@expo/json-file": "~10.0.8", "@expo/plist": "^0.4.8", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q=="],
+
+ "@expo/config-types": ["@expo/config-types@54.0.10", "", {}, "sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA=="],
+
+ "@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="],
+
+ "@expo/devtools": ["@expo/devtools@0.1.8", "", { "dependencies": { "chalk": "^4.1.2" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-SVLxbuanDjJPgc0sy3EfXUMLb/tXzp6XIHkhtPVmTWJAp+FOr6+5SeiCfJrCzZFet0Ifyke2vX3sFcKwEvCXwQ=="],
+
+ "@expo/env": ["@expo/env@2.0.8", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" } }, "sha512-5VQD6GT8HIMRaSaB5JFtOXuvfDVU80YtZIuUT/GDhUF782usIXY13Tn3IdDz1Tm/lqA9qnRZQ1BF4t7LlvdJPA=="],
+
+ "@expo/fingerprint": ["@expo/fingerprint@0.15.4", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^9.0.0", "p-limit": "^3.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-eYlxcrGdR2/j2M6pEDXo9zU9KXXF1vhP+V+Tl+lyY+bU8lnzrN6c637mz6Ye3em2ANy8hhUR03Raf8VsT9Ogng=="],
+
+ "@expo/image-utils": ["@expo/image-utils@0.8.8", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "resolve-global": "^1.0.0", "semver": "^7.6.0", "temp-dir": "~2.0.0", "unique-string": "~2.0.0" } }, "sha512-HHHaG4J4nKjTtVa1GG9PCh763xlETScfEyNxxOvfTRr8IKPJckjTyqSLEtdJoFNJ1vqiABEjW7tqGhqGibZLeA=="],
+
+ "@expo/json-file": ["@expo/json-file@10.0.8", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ=="],
+
+ "@expo/metro": ["@expo/metro@54.2.0", "", { "dependencies": { "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-minify-terser": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3" } }, "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w=="],
+
+ "@expo/metro-config": ["@expo/metro-config@54.0.14", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~12.0.13", "@expo/env": "~2.0.8", "@expo/json-file": "~10.0.8", "@expo/metro": "~54.2.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.29.1", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "minimatch": "^9.0.0", "postcss": "~8.4.32", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA=="],
+
+ "@expo/metro-runtime": ["@expo/metro-runtime@6.1.2", "", { "dependencies": { "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-nvM+Qv45QH7pmYvP8JB1G8JpScrWND3KrMA6ZKe62cwwNiX/BjHU28Ear0v/4bQWXlOY0mv6B8CDIm8JxXde9g=="],
+
+ "@expo/osascript": ["@expo/osascript@2.3.8", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "exec-async": "^2.2.0" } }, "sha512-/TuOZvSG7Nn0I8c+FcEaoHeBO07yu6vwDgk7rZVvAXoeAK5rkA09jRyjYsZo+0tMEFaToBeywA6pj50Mb3ny9w=="],
+
+ "@expo/package-manager": ["@expo/package-manager@1.9.10", "", { "dependencies": { "@expo/json-file": "^10.0.8", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA=="],
+
+ "@expo/plist": ["@expo/plist@0.4.8", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.2.3", "xmlbuilder": "^15.1.1" } }, "sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ=="],
+
+ "@expo/prebuild-config": ["@expo/prebuild-config@54.0.8", "", { "dependencies": { "@expo/config": "~12.0.13", "@expo/config-plugins": "~54.0.4", "@expo/config-types": "^54.0.10", "@expo/image-utils": "^0.8.8", "@expo/json-file": "^10.0.8", "@react-native/normalize-colors": "0.81.5", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-EA7N4dloty2t5Rde+HP0IEE+nkAQiu4A/+QGZGT9mFnZ5KKjPPkqSyYcRvP5bhQE10D+tvz6X0ngZpulbMdbsg=="],
+
+ "@expo/schema-utils": ["@expo/schema-utils@0.1.8", "", {}, "sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A=="],
+
+ "@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
+
+ "@expo/spawn-async": ["@expo/spawn-async@1.7.2", "", { "dependencies": { "cross-spawn": "^7.0.3" } }, "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew=="],
+
+ "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
+
+ "@expo/vector-icons": ["@expo/vector-icons@15.0.3", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA=="],
+
+ "@expo/ws-tunnel": ["@expo/ws-tunnel@1.0.6", "", {}, "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q=="],
+
+ "@expo/xcpretty": ["@expo/xcpretty@4.4.0", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw=="],
+
+ "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
+
+ "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.1", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ=="],
+
+ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
+
+ "@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="],
+
+ "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
+
+ "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
+
+ "@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="],
+
+ "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
+
+ "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
+
+ "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
+
+ "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
+
+ "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
+
+ "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
+
+ "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
+
+ "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
+
+ "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
+
+ "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
+
+ "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
+
+ "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
+
+ "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
+
+ "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
+
+ "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
+
+ "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
+
+ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+
+ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
+
+ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
+
+ "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
+
+ "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
+
+ "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
+
+ "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
+
+ "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
+
+ "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
+
+ "@react-native/assets-registry": ["@react-native/assets-registry@0.81.5", "", {}, "sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w=="],
+
+ "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.81.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.81.5" } }, "sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ=="],
+
+ "@react-native/babel-preset": ["@react-native/babel-preset@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.81.5", "babel-plugin-syntax-hermes-parser": "0.29.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA=="],
+
+ "@react-native/codegen": ["@react-native/codegen@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g=="],
+
+ "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.81.5", "", { "dependencies": { "@react-native/dev-middleware": "0.81.5", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.1", "metro-config": "^0.83.1", "metro-core": "^0.83.1", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-yWRlmEOtcyvSZ4+OvqPabt+NS36vg0K/WADTQLhrYrm9qdZSuXmq8PmdJWz/68wAqKQ+4KTILiq2kjRQwnyhQw=="],
+
+ "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.5", "", {}, "sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w=="],
+
+ "@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.5", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.5", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA=="],
+
+ "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.81.5", "", {}, "sha512-hORRlNBj+ReNMLo9jme3yQ6JQf4GZpVEBLxmTXGGlIL78MAezDZr5/uq9dwElSbcGmLEgeiax6e174Fie6qPLg=="],
+
+ "@react-native/js-polyfills": ["@react-native/js-polyfills@0.81.5", "", {}, "sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w=="],
+
+ "@react-native/normalize-colors": ["@react-native/normalize-colors@0.81.5", "", {}, "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g=="],
+
+ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.81.5", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "*", "react-native": "*" } }, "sha512-UVXgV/db25OPIvwZySeToXD/9sKKhOdkcWmmf4Jh8iBZuyfML+/5CasaZ1E7Lqg6g3uqVQq75NqIwkYmORJMPw=="],
+
+ "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.12.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-/GtOfVWRligHG0mvX39I1FGdUWeWl0GVF2okEziQSQj0bOTrLIt7y44C3r/aCLkEpTVltCPGM3swqGTH3UfRCw=="],
+
+ "@react-navigation/core": ["@react-navigation/core@7.14.0", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g=="],
+
+ "@react-navigation/elements": ["@react-navigation/elements@2.9.5", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g=="],
+
+ "@react-navigation/native": ["@react-navigation/native@7.1.28", "", { "dependencies": { "@react-navigation/core": "^7.14.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ=="],
+
+ "@react-navigation/native-stack": ["@react-navigation/native-stack@7.12.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ=="],
+
+ "@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
+
+ "@shopify/react-native-skia": ["@shopify/react-native-skia@2.4.18", "", { "dependencies": { "canvaskit-wasm": "0.40.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "react": ">=19.0", "react-native": ">=0.78", "react-native-reanimated": ">=3.19.1" }, "bin": { "setup-skia-web": "scripts/setup-canvaskit.js" } }, "sha512-/AB/mvb2dGSQVIJTxyG9ZMFn2PjwWlVcFxHU4TV+K25LCU49ntJXl4xda2ghXxdENMggKgO9R5pA+P/AQ0UUrA=="],
+
+ "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
+
+ "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
+
+ "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
+
+ "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
+
+ "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
+
+ "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
+
+ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
+
+ "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
+
+ "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
+
+ "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
+
+ "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
+
+ "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
+
+ "@types/node": ["@types/node@22.19.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw=="],
+
+ "@types/react": ["@types/react@19.1.17", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA=="],
+
+ "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
+
+ "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="],
+
+ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
+
+ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
+
+ "@urql/core": ["@urql/core@5.2.0", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.13", "wonka": "^6.3.2" } }, "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A=="],
+
+ "@urql/exchange-retry": ["@urql/exchange-retry@1.3.2", "", { "dependencies": { "@urql/core": "^5.1.2", "wonka": "^6.3.2" }, "peerDependencies": { "@urql/core": "^5.0.0" } }, "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg=="],
+
+ "@webgpu/types": ["@webgpu/types@0.1.21", "", {}, "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow=="],
+
+ "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
+
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
+
+ "acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+ "anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="],
+
+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
+
+ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+
+ "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
+
+ "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
+
+ "async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="],
+
+ "await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="],
+
+ "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
+
+ "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
+
+ "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
+
+ "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="],
+
+ "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
+
+ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="],
+
+ "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
+
+ "babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.21.2", "", {}, "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA=="],
+
+ "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="],
+
+ "babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
+
+ "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
+
+ "babel-preset-expo": ["babel-preset-expo@54.0.10", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.81.5", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.29.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "react-refresh": ">=0.14.0 <1.0.0" } }, "sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw=="],
+
+ "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": "dist/cli.js" }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
+
+ "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="],
+
+ "better-sqlite3": ["better-sqlite3@11.10.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ=="],
+
+ "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
+
+ "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
+
+ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
+
+ "bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="],
+
+ "bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
+
+ "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
+
+ "browserslist": ["browserslist@4.28.1", "", { "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" }, "bin": "cli.js" }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
+
+ "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
+
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
+
+ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="],
+
+ "canvaskit-wasm": ["canvaskit-wasm@0.40.0", "", { "dependencies": { "@webgpu/types": "0.1.21" } }, "sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw=="],
+
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
+
+ "chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="],
+
+ "chromium-edge-launcher": ["chromium-edge-launcher@0.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg=="],
+
+ "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
+
+ "cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
+
+ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
+
+ "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
+
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
+
+ "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
+
+ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
+
+ "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
+
+ "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="],
+
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
+ "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
+
+ "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "convex": ["convex@1.31.7", "", { "dependencies": { "esbuild": "0.27.0", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react"], "bin": "bin/main.js" }, "sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ=="],
+
+ "core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="],
+
+ "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="],
+
+ "css-in-js-utils": ["css-in-js-utils@3.1.0", "", { "dependencies": { "hyphenate-style-name": "^1.0.3" } }, "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A=="],
+
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
+
+ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
+
+ "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
+
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
+ "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
+
+ "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
+
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
+
+ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
+
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
+
+ "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
+
+ "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="],
+
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
+
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+
+ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+
+ "env-editor": ["env-editor@0.4.2", "", {}, "sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA=="],
+
+ "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="],
+
+ "esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": "bin/esbuild" }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
+
+ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
+
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "exec-async": ["exec-async@2.2.0", "", {}, "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw=="],
+
+ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
+
+ "expo": ["expo@54.0.33", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", "@expo/config": "~12.0.13", "@expo/config-plugins": "~54.0.4", "@expo/devtools": "0.1.8", "@expo/fingerprint": "0.15.4", "@expo/metro": "~54.2.0", "@expo/metro-config": "54.0.14", "@expo/vector-icons": "^15.0.3", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~54.0.10", "expo-asset": "~12.0.12", "expo-constants": "~18.0.13", "expo-file-system": "~19.0.21", "expo-font": "~14.0.11", "expo-keep-awake": "~15.0.8", "expo-modules-autolinking": "3.0.24", "expo-modules-core": "3.0.29", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "react-native-webview"], "bin": { "expo": "bin/cli", "expo-modules-autolinking": "bin/autolinking", "fingerprint": "bin/fingerprint" } }, "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw=="],
+
+ "expo-asset": ["expo-asset@12.0.12", "", { "dependencies": { "@expo/image-utils": "^0.8.8", "expo-constants": "~18.0.12" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ=="],
+
+ "expo-camera": ["expo-camera@17.0.10", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-w1RBw83mAGVk4BPPwNrCZyFop0VLiVSRE3c2V9onWbdFwonpRhzmB4drygG8YOUTl1H3wQvALJHyMPTbgsK1Jg=="],
+
+ "expo-constants": ["expo-constants@18.0.13", "", { "dependencies": { "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ=="],
+
+ "expo-file-system": ["expo-file-system@19.0.21", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg=="],
+
+ "expo-font": ["expo-font@14.0.11", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg=="],
+
+ "expo-image-loader": ["expo-image-loader@6.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ=="],
+
+ "expo-image-manipulator": ["expo-image-manipulator@14.0.8", "", { "dependencies": { "expo-image-loader": "~6.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-sXsXjm7rIxLWZe0j2A41J/Ph53PpFJRdyzJ3EQ/qetxLUvS2m3K1sP5xy37px43qCf0l79N/i6XgFgenFV36/Q=="],
+
+ "expo-keep-awake": ["expo-keep-awake@15.0.8", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ=="],
+
+ "expo-linking": ["expo-linking@8.0.11", "", { "dependencies": { "expo-constants": "~18.0.12", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA=="],
+
+ "expo-modules-autolinking": ["expo-modules-autolinking@3.0.24", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0" }, "bin": "bin/expo-modules-autolinking.js" }, "sha512-TP+6HTwhL7orDvsz2VzauyQlXJcAWyU3ANsZ7JGL4DQu8XaZv/A41ZchbtAYLfozNA2Ya1Hzmhx65hXryBMjaQ=="],
+
+ "expo-modules-core": ["expo-modules-core@3.0.29", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q=="],
+
+ "expo-router": ["expo-router@6.0.23", "", { "dependencies": { "@expo/metro-runtime": "^6.1.2", "@expo/schema-utils": "^0.1.8", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/native": "^7.1.8", "@react-navigation/native-stack": "^7.3.16", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-server": "^1.0.5", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@expo/metro-runtime": "^6.1.2", "@react-navigation/drawer": "^7.5.0", "@testing-library/react-native": ">= 12.0.0", "expo": "*", "expo-constants": "^18.0.13", "expo-linking": "^8.0.11", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-native-gesture-handler", "react-server-dom-webpack"] }, "sha512-qCxVAiCrCyu0npky6azEZ6dJDMt77OmCzEbpF6RbUTlfkaCA417LvY14SBkk0xyGruSxy/7pvJOI6tuThaUVCA=="],
+
+ "expo-server": ["expo-server@1.0.5", "", {}, "sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA=="],
+
+ "expo-splash-screen": ["expo-splash-screen@31.0.13", "", { "dependencies": { "@expo/prebuild-config": "^54.0.8" }, "peerDependencies": { "expo": "*" } }, "sha512-1epJLC1cDlwwj089R2h8cxaU5uk4ONVAC+vzGiTZH4YARQhL4Stlz1MbR6yAS173GMosvkE6CAeihR7oIbCkDA=="],
+
+ "expo-sqlite": ["expo-sqlite@16.0.10", "", { "dependencies": { "await-lock": "^2.2.2" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-tUOKxE9TpfneRG3eOfbNfhN9236SJ7IiUnP8gCqU7umd9DtgDGB/5PhYVVfl+U7KskgolgNoB9v9OZ9iwXN8Eg=="],
+
+ "expo-status-bar": ["expo-status-bar@3.0.9", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-xyYyVg6V1/SSOZWh4Ni3U129XHCnFHBTcUo0dhWtFDrZbNp/duw5AGsQfb2sVeU0gxWHXSY1+5F0jnKYC7WuOw=="],
+
+ "expo-web-browser": ["expo-web-browser@15.0.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg=="],
+
+ "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
+
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
+
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+
+ "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
+
+ "fbjs": ["fbjs@3.0.5", "", { "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^1.0.35" } }, "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg=="],
+
+ "fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
+
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+
+ "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="],
+
+ "finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="],
+
+ "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+ "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw=="],
+
+ "fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="],
+
+ "freeport-async": ["freeport-async@2.0.0", "", {}, "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ=="],
+
+ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
+
+ "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
+
+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
+
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+ "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
+
+ "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
+
+ "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
+
+ "getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
+
+ "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
+
+ "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "global-dirs": ["global-dirs@0.1.1", "", { "dependencies": { "ini": "^1.3.4" } }, "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
+
+ "hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
+
+ "hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
+
+ "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
+
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
+
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+ "hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
+ "image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": "bin/image-size.js" }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
+
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+
+ "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
+
+ "inline-style-prefixer": ["inline-style-prefixer@7.0.1", "", { "dependencies": { "css-in-js-utils": "^3.1.0" } }, "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw=="],
+
+ "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
+
+ "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
+
+ "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
+
+ "is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
+
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+
+ "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
+
+ "istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
+
+ "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
+
+ "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
+
+ "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
+
+ "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
+
+ "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
+
+ "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
+
+ "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
+
+ "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
+
+ "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
+
+ "jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="],
+
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+
+ "jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="],
+
+ "jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
+ "json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+
+ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
+
+ "lan-network": ["lan-network@0.1.7", "", { "bin": "dist/lan-network-cli.js" }, "sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ=="],
+
+ "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
+
+ "lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
+
+ "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
+
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
+
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
+
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
+
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
+
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
+
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
+
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
+
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
+
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
+
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
+
+ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
+
+ "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
+
+ "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
+
+ "log-symbols": ["log-symbols@2.2.0", "", { "dependencies": { "chalk": "^2.0.1" } }, "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg=="],
+
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+
+ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
+ "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
+
+ "marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
+
+ "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
+
+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
+
+ "metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": "src/cli.js" }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="],
+
+ "metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="],
+
+ "metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="],
+
+ "metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="],
+
+ "metro-config": ["metro-config@0.83.3", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.3", "metro-cache": "0.83.3", "metro-core": "0.83.3", "metro-runtime": "0.83.3", "yaml": "^2.6.1" } }, "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA=="],
+
+ "metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="],
+
+ "metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="],
+
+ "metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="],
+
+ "metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="],
+
+ "metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="],
+
+ "metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="],
+
+ "metro-symbolicate": ["metro-symbolicate@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": "src/index.js" }, "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw=="],
+
+ "metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="],
+
+ "metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="],
+
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+
+ "mime": ["mime@1.6.0", "", { "bin": "cli.js" }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
+
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+ "mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
+
+ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
+
+ "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
+
+ "mkdirp": ["mkdirp@1.0.4", "", { "bin": "bin/cmd.js" }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
+
+ "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
+
+ "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
+
+ "nested-error-stacks": ["nested-error-stacks@2.0.1", "", {}, "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A=="],
+
+ "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
+
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
+
+ "node-forge": ["node-forge@1.3.3", "", {}, "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg=="],
+
+ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
+
+ "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
+
+ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+
+ "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
+
+ "nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="],
+
+ "ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="],
+
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
+
+ "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
+
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+ "onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
+
+ "open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
+
+ "ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
+
+ "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
+
+ "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
+
+ "parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
+
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
+
+ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+
+ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@3.0.1", "", {}, "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag=="],
+
+ "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
+
+ "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
+
+ "pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
+
+ "postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="],
+
+ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
+
+ "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
+
+ "prettier": ["prettier@3.8.1", "", { "bin": "bin/prettier.cjs" }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
+
+ "pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="],
+
+ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
+
+ "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="],
+
+ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
+
+ "promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="],
+
+ "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
+
+ "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
+
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
+ "qrcode-terminal": ["qrcode-terminal@0.11.0", "", { "bin": "bin/qrcode-terminal.js" }, "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ=="],
+
+ "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
+
+ "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
+
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
+
+ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": "cli.js" }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
+
+ "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
+
+ "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
+
+ "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
+
+ "react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="],
+
+ "react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="],
+
+ "react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],
+
+ "react-native": ["react-native@0.81.5", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", "@react-native/codegen": "0.81.5", "@react-native/community-cli-plugin": "0.81.5", "@react-native/gradle-plugin": "0.81.5", "@react-native/js-polyfills": "0.81.5", "@react-native/normalize-colors": "0.81.5", "@react-native/virtualized-lists": "0.81.5", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.29.1", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "^19.1.0" }, "bin": "cli.js" }, "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw=="],
+
+ "react-native-fast-opencv": ["react-native-fast-opencv@0.4.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-0CAhHzMJni2nTPtaZundvTE06gXtElU0VRa2oUmypJPzvXOvDYQMi49P9mDtrIUQS4Jnf1OvRuGPQ1q/J8hMyQ=="],
+
+ "react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
+
+ "react-native-reanimated": ["react-native-reanimated@4.1.6", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*", "react-native-worklets": ">=0.5.0" } }, "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ=="],
+
+ "react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
+
+ "react-native-screens": ["react-native-screens@4.16.0", "", { "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q=="],
+
+ "react-native-vision-camera": ["react-native-vision-camera@4.7.3", "", { "peerDependencies": { "@shopify/react-native-skia": "*", "react": "*", "react-native": "*", "react-native-reanimated": "*", "react-native-worklets-core": "*" } }, "sha512-g1/neOyjSqn1kaAa2FxI/qp5KzNvPcF0bnQw6NntfbxH6tm0+8WFZszlgb5OV+iYlB6lFUztCbDtyz5IpL47OA=="],
+
+ "react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
+
+ "react-native-worklets": ["react-native-worklets@0.5.1", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w=="],
+
+ "react-native-worklets-core": ["react-native-worklets-core@1.6.2", "", { "dependencies": { "string-hash-64": "^1.0.3" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zw73JfL40ZL/OD2TOil1El4D9ZwS3l6AFPeFfUWXh+V2/dHN8i28jHX8QXlz5DYtAkR+Ju3U1h4yiaODi/igZw=="],
+
+ "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="],
+
+ "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
+
+ "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
+
+ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
+
+ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
+
+ "react-test-renderer": ["react-test-renderer@19.1.0", "", { "dependencies": { "react-is": "^19.1.0", "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw=="],
+
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
+
+ "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
+
+ "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="],
+
+ "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
+
+ "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
+
+ "regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": "bin/parser" }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="],
+
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+
+ "requireg": ["requireg@0.2.2", "", { "dependencies": { "nested-error-stacks": "~2.0.1", "rc": "~1.2.7", "resolve": "~1.7.1" } }, "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg=="],
+
+ "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
+
+ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+
+ "resolve-global": ["resolve-global@1.0.0", "", { "dependencies": { "global-dirs": "^0.1.1" } }, "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw=="],
+
+ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+
+ "resolve-workspace-root": ["resolve-workspace-root@2.0.1", "", {}, "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w=="],
+
+ "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
+
+ "restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="],
+
+ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
+
+ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
+
+ "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
+
+ "semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
+
+ "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
+
+ "serialize-error": ["serialize-error@2.1.0", "", {}, "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw=="],
+
+ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
+
+ "server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
+
+ "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
+
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
+
+ "sf-symbols-typescript": ["sf-symbols-typescript@2.2.0", "", {}, "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw=="],
+
+ "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
+
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
+
+ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
+
+ "simple-plist": ["simple-plist@1.3.1", "", { "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", "plist": "^3.0.5" } }, "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw=="],
+
+ "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
+
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+
+ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+
+ "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
+
+ "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
+
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
+ "split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
+
+ "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+
+ "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
+
+ "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
+
+ "stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
+
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
+
+ "stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="],
+
+ "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
+
+ "string-hash-64": ["string-hash-64@1.0.3", "", {}, "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw=="],
+
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
+
+ "structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="],
+
+ "styleq": ["styleq@0.1.3", "", {}, "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="],
+
+ "sucrase": ["sucrase@3.35.1", "", { "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" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
+
+ "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
+ "supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="],
+
+ "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
+
+ "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
+
+ "temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="],
+
+ "terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
+
+ "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": "bin/terser" }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
+
+ "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
+
+ "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
+
+ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
+
+ "throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="],
+
+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+ "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
+
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
+
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
+ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
+ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
+
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
+
+ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
+
+ "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
+
+ "type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
+
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+ "ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": "script/cli.js" }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
+
+ "undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="],
+
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
+
+ "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
+
+ "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
+
+ "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
+
+ "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
+
+ "unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="],
+
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
+
+ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
+
+ "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
+
+ "use-latest-callback": ["use-latest-callback@0.2.6", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg=="],
+
+ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
+
+ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
+
+ "uuid": ["uuid@7.0.3", "", { "bin": "dist/bin/uuid" }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
+
+ "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
+
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
+
+ "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
+
+ "vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="],
+
+ "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
+
+ "warn-once": ["warn-once@0.1.1", "", {}, "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q=="],
+
+ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
+
+ "webidl-conversions": ["webidl-conversions@5.0.0", "", {}, "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="],
+
+ "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
+
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+
+ "whatwg-url-without-unicode": ["whatwg-url-without-unicode@8.0.0-3", "", { "dependencies": { "buffer": "^5.4.3", "punycode": "^2.1.1", "webidl-conversions": "^5.0.0" } }, "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "wonka": ["wonka@6.3.5", "", {}, "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw=="],
+
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+ "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
+
+ "ws": ["ws@6.2.3", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA=="],
+
+ "xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
+
+ "xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
+
+ "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
+
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
+
+ "yaml": ["yaml@2.8.2", "", { "bin": "bin.mjs" }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
+
+ "yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+
+ "zustand": ["zustand@5.0.11", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["immer"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="],
+
+ "@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "@babel/highlight/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "@babel/template/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@babel/traverse--for-generate-function-map/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@expo/cli/glob": ["glob@13.0.1", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="],
+
+ "@expo/cli/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@expo/cli/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
+
+ "@expo/config/glob": ["glob@13.0.1", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="],
+
+ "@expo/config/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@expo/config-plugins/glob": ["glob@13.0.1", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="],
+
+ "@expo/config-plugins/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+
+ "@expo/fingerprint/glob": ["glob@13.0.1", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="],
+
+ "@expo/fingerprint/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@expo/image-utils/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
+
+ "@expo/metro-config/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@expo/metro-config/glob": ["glob@13.0.1", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="],
+
+ "@expo/prebuild-config/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@expo/xcpretty/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
+
+ "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
+
+ "@jest/environment/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "@jest/fake-timers/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "@jest/types/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
+
+ "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+
+ "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+
+ "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+
+ "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+
+ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "@react-native/community-cli-plugin/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "@react-native/dev-middleware/ws": ["ws@6.2.3", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA=="],
+
+ "@types/better-sqlite3/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "@types/graceful-fs/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
+
+ "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
+
+ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "chrome-launcher/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "chromium-edge-launcher/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
+
+ "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
+
+ "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
+
+ "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
+
+ "finalhandler/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="],
+
+ "finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
+
+ "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
+
+ "istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "jest-environment-node/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "jest-haste-map/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "jest-message-util/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "jest-mock/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "jest-util/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
+
+ "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "jest-worker/@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
+
+ "lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+
+ "metro/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
+
+ "metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
+
+ "metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
+
+ "metro-babel-transformer/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
+
+ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "node-abi/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "npm-package-arg/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
+ "ora/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
+
+ "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+
+ "path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
+
+ "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
+ "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+
+ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
+
+ "react-native/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "react-native-reanimated/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
+ "react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
+
+ "react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
+
+ "react-native-worklets/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
+ "react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
+
+ "requireg/resolve": ["resolve@1.7.1", "", { "dependencies": { "path-parse": "^1.0.5" } }, "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw=="],
+
+ "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "serve-static/encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+
+ "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
+
+ "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
+
+ "supports-hyperlinks/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
+
+ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
+
+ "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "test-exclude/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+
+ "whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+
+ "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
+
+ "@babel/highlight/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "@babel/highlight/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "@babel/highlight/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "@expo/cli/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
+
+ "@expo/config-plugins/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
+
+ "@expo/config/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
+
+ "@expo/fingerprint/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
+
+ "@expo/metro-config/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
+
+ "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
+
+ "@jest/environment/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "@jest/fake-timers/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "@jest/types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@react-native/codegen/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "@types/better-sqlite3/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "@types/graceful-fs/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "chrome-launcher/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "chromium-edge-launcher/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "jest-environment-node/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "jest-haste-map/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "jest-mock/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "jest-util/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "jest-worker/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "log-symbols/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
+
+ "metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
+
+ "ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
+
+ "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "@babel/highlight/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "@babel/highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+ }
+}
diff --git a/src/Scry.App/Resources/Raw/card_hashes.db b/card_hashes.db
similarity index 100%
rename from src/Scry.App/Resources/Raw/card_hashes.db
rename to card_hashes.db
diff --git a/components/EditScreenInfo.tsx b/components/EditScreenInfo.tsx
new file mode 100644
index 0000000..430b609
--- /dev/null
+++ b/components/EditScreenInfo.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+
+import { ExternalLink } from './ExternalLink';
+import { MonoText } from './StyledText';
+import { Text, View } from './Themed';
+
+import Colors from '@/constants/Colors';
+
+export default function EditScreenInfo({ path }: { path: string }) {
+ return (
+
+
+
+ Open up the code for this screen:
+
+
+
+ {path}
+
+
+
+ Change any of the text, save the file, and your app will automatically update.
+
+
+
+
+
+
+ Tap here if your app doesn't automatically update after making changes
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ getStartedContainer: {
+ alignItems: 'center',
+ marginHorizontal: 50,
+ },
+ homeScreenFilename: {
+ marginVertical: 7,
+ },
+ codeHighlightContainer: {
+ borderRadius: 3,
+ paddingHorizontal: 4,
+ },
+ getStartedText: {
+ fontSize: 17,
+ lineHeight: 24,
+ textAlign: 'center',
+ },
+ helpContainer: {
+ marginTop: 15,
+ marginHorizontal: 20,
+ alignItems: 'center',
+ },
+ helpLink: {
+ paddingVertical: 15,
+ },
+ helpLinkText: {
+ textAlign: 'center',
+ },
+});
diff --git a/components/ExternalLink.tsx b/components/ExternalLink.tsx
new file mode 100644
index 0000000..6957b3f
--- /dev/null
+++ b/components/ExternalLink.tsx
@@ -0,0 +1,25 @@
+import { Link } from 'expo-router';
+import * as WebBrowser from 'expo-web-browser';
+import React from 'react';
+import { Platform } from 'react-native';
+
+export function ExternalLink(
+ props: Omit, 'href'> & { href: string }
+) {
+ return (
+ {
+ if (Platform.OS !== 'web') {
+ // Prevent the default behavior of linking to the default browser on native.
+ e.preventDefault();
+ // Open the link in an in-app browser.
+ WebBrowser.openBrowserAsync(props.href as string);
+ }
+ }}
+ />
+ );
+}
diff --git a/components/StyledText.tsx b/components/StyledText.tsx
new file mode 100644
index 0000000..aa3977c
--- /dev/null
+++ b/components/StyledText.tsx
@@ -0,0 +1,5 @@
+import { Text, TextProps } from './Themed';
+
+export function MonoText(props: TextProps) {
+ return ;
+}
diff --git a/components/Themed.tsx b/components/Themed.tsx
new file mode 100644
index 0000000..9139f9b
--- /dev/null
+++ b/components/Themed.tsx
@@ -0,0 +1,45 @@
+/**
+ * Learn more about Light and Dark modes:
+ * https://docs.expo.io/guides/color-schemes/
+ */
+
+import { Text as DefaultText, View as DefaultView } from 'react-native';
+
+import Colors from '@/constants/Colors';
+import { useColorScheme } from './useColorScheme';
+
+type ThemeProps = {
+ lightColor?: string;
+ darkColor?: string;
+};
+
+export type TextProps = ThemeProps & DefaultText['props'];
+export type ViewProps = ThemeProps & DefaultView['props'];
+
+export function useThemeColor(
+ props: { light?: string; dark?: string },
+ colorName: keyof typeof Colors.light & keyof typeof Colors.dark
+) {
+ const theme = useColorScheme() ?? 'light';
+ const colorFromProps = props[theme];
+
+ if (colorFromProps) {
+ return colorFromProps;
+ } else {
+ return Colors[theme][colorName];
+ }
+}
+
+export function Text(props: TextProps) {
+ const { style, lightColor, darkColor, ...otherProps } = props;
+ const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
+
+ return ;
+}
+
+export function View(props: ViewProps) {
+ const { style, lightColor, darkColor, ...otherProps } = props;
+ const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
+
+ return ;
+}
diff --git a/components/__tests__/StyledText-test.js b/components/__tests__/StyledText-test.js
new file mode 100644
index 0000000..f569ce8
--- /dev/null
+++ b/components/__tests__/StyledText-test.js
@@ -0,0 +1,10 @@
+import * as React from 'react';
+import renderer from 'react-test-renderer';
+
+import { MonoText } from '../StyledText';
+
+it(`renders correctly`, () => {
+ const tree = renderer.create(Snapshot test!).toJSON();
+
+ expect(tree).toMatchSnapshot();
+});
diff --git a/components/camera/ExpoCamera.tsx b/components/camera/ExpoCamera.tsx
new file mode 100644
index 0000000..6e6d4af
--- /dev/null
+++ b/components/camera/ExpoCamera.tsx
@@ -0,0 +1,48 @@
+/**
+ * Camera component using expo-camera (works in Expo Go).
+ */
+
+import React, { forwardRef, useImperativeHandle, useRef } from "react";
+import { StyleSheet } from "react-native";
+import { CameraView } from "expo-camera";
+
+export interface CameraHandle {
+ takePhoto: () => Promise<{ uri: string }>;
+}
+
+interface ExpoCameraProps {
+ flashEnabled?: boolean;
+}
+
+export const ExpoCamera = forwardRef(
+ ({ flashEnabled = false }, ref) => {
+ const cameraRef = useRef(null);
+
+ useImperativeHandle(ref, () => ({
+ takePhoto: async () => {
+ if (!cameraRef.current) {
+ throw new Error("Camera not ready");
+ }
+ const photo = await cameraRef.current.takePictureAsync({
+ quality: 0.8,
+ base64: false,
+ });
+ if (!photo) {
+ throw new Error("Failed to capture photo");
+ }
+ return { uri: photo.uri };
+ },
+ }));
+
+ return (
+
+ );
+ }
+);
+
+ExpoCamera.displayName = "ExpoCamera";
diff --git a/components/camera/VisionCamera.tsx b/components/camera/VisionCamera.tsx
new file mode 100644
index 0000000..1603c88
--- /dev/null
+++ b/components/camera/VisionCamera.tsx
@@ -0,0 +1,53 @@
+/**
+ * Camera component using react-native-vision-camera (production builds).
+ */
+
+import React, { forwardRef, useImperativeHandle, useRef } from "react";
+import { StyleSheet } from "react-native";
+import { Camera, useCameraDevice } from "react-native-vision-camera";
+
+export interface CameraHandle {
+ takePhoto: () => Promise<{ uri: string }>;
+}
+
+interface VisionCameraProps {
+ flashEnabled?: boolean;
+}
+
+export const VisionCamera = forwardRef(
+ ({ flashEnabled = false }, ref) => {
+ const device = useCameraDevice("back");
+ const cameraRef = useRef(null);
+
+ useImperativeHandle(ref, () => ({
+ takePhoto: async () => {
+ if (!cameraRef.current) {
+ throw new Error("Camera not ready");
+ }
+ const photo = await cameraRef.current.takePhoto({
+ flash: flashEnabled ? "on" : "off",
+ enableShutterSound: false,
+ });
+ return { uri: photo.path };
+ },
+ }));
+
+ if (!device) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+);
+
+VisionCamera.displayName = "VisionCamera";
+
+export { useCameraDevice };
diff --git a/components/camera/index.tsx b/components/camera/index.tsx
new file mode 100644
index 0000000..409cba4
--- /dev/null
+++ b/components/camera/index.tsx
@@ -0,0 +1,64 @@
+/**
+ * Adaptive camera component that uses expo-camera in Expo Go
+ * and react-native-vision-camera in production builds.
+ */
+
+import React, { forwardRef, lazy, Suspense } from "react";
+import { View, ActivityIndicator, StyleSheet, Text } from "react-native";
+import Constants from "expo-constants";
+
+export interface CameraHandle {
+ takePhoto: () => Promise<{ uri: string }>;
+}
+
+interface AdaptiveCameraProps {
+ flashEnabled?: boolean;
+}
+
+// Detect if running in Expo Go
+const isExpoGo = Constants.appOwnership === "expo";
+
+// Lazy load the appropriate camera component
+const ExpoCamera = lazy(() =>
+ import("./ExpoCamera").then((m) => ({ default: m.ExpoCamera }))
+);
+const VisionCamera = lazy(() =>
+ import("./VisionCamera").then((m) => ({ default: m.VisionCamera }))
+);
+
+const CameraLoading = () => (
+
+
+ Initializing camera...
+
+);
+
+export const AdaptiveCamera = forwardRef(
+ (props, ref) => {
+ const CameraComponent = isExpoGo ? ExpoCamera : VisionCamera;
+
+ return (
+ }>
+
+
+ );
+ }
+);
+
+AdaptiveCamera.displayName = "AdaptiveCamera";
+
+const styles = StyleSheet.create({
+ loading: {
+ ...StyleSheet.absoluteFillObject,
+ backgroundColor: "#000",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ loadingText: {
+ marginTop: 16,
+ color: "#888",
+ fontSize: 14,
+ },
+});
+
+export { isExpoGo };
diff --git a/components/useClientOnlyValue.ts b/components/useClientOnlyValue.ts
new file mode 100644
index 0000000..4b917ef
--- /dev/null
+++ b/components/useClientOnlyValue.ts
@@ -0,0 +1,4 @@
+// This function is web-only as native doesn't currently support server (or build-time) rendering.
+export function useClientOnlyValue(server: S, client: C): S | C {
+ return client;
+}
diff --git a/components/useClientOnlyValue.web.ts b/components/useClientOnlyValue.web.ts
new file mode 100644
index 0000000..625bc8a
--- /dev/null
+++ b/components/useClientOnlyValue.web.ts
@@ -0,0 +1,12 @@
+import React from 'react';
+
+// `useEffect` is not invoked during server rendering, meaning
+// we can use this to determine if we're on the server or not.
+export function useClientOnlyValue(server: S, client: C): S | C {
+ const [value, setValue] = React.useState(server);
+ React.useEffect(() => {
+ setValue(client);
+ }, [client]);
+
+ return value;
+}
diff --git a/components/useColorScheme.ts b/components/useColorScheme.ts
new file mode 100644
index 0000000..17e3c63
--- /dev/null
+++ b/components/useColorScheme.ts
@@ -0,0 +1 @@
+export { useColorScheme } from 'react-native';
diff --git a/components/useColorScheme.web.ts b/components/useColorScheme.web.ts
new file mode 100644
index 0000000..6dcd80d
--- /dev/null
+++ b/components/useColorScheme.web.ts
@@ -0,0 +1,8 @@
+// NOTE: The default React Native styling doesn't support server rendering.
+// Server rendered styles should not change between the first render of the HTML
+// and the first render on the client. Typically, web developers will use CSS media queries
+// to render different styles on the client and server, these aren't directly supported in React Native
+// but can be achieved using a styling library like Nativewind.
+export function useColorScheme() {
+ return 'light';
+}
diff --git a/constants/Colors.ts b/constants/Colors.ts
new file mode 100644
index 0000000..1c706c7
--- /dev/null
+++ b/constants/Colors.ts
@@ -0,0 +1,19 @@
+const tintColorLight = '#2f95dc';
+const tintColorDark = '#fff';
+
+export default {
+ light: {
+ text: '#000',
+ background: '#fff',
+ tint: tintColorLight,
+ tabIconDefault: '#ccc',
+ tabIconSelected: tintColorLight,
+ },
+ dark: {
+ text: '#fff',
+ background: '#000',
+ tint: tintColorDark,
+ tabIconDefault: '#ccc',
+ tabIconSelected: tintColorDark,
+ },
+};
diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts
new file mode 100644
index 0000000..018bfc3
--- /dev/null
+++ b/convex/_generated/api.d.ts
@@ -0,0 +1,59 @@
+/* eslint-disable */
+/**
+ * Generated `api` utility.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import type * as auth from "../auth.js";
+import type * as cards from "../cards.js";
+import type * as collections from "../collections.js";
+import type * as http from "../http.js";
+import type * as scanHistory from "../scanHistory.js";
+import type * as users from "../users.js";
+
+import type {
+ ApiFromModules,
+ FilterApi,
+ FunctionReference,
+} from "convex/server";
+
+declare const fullApi: ApiFromModules<{
+ auth: typeof auth;
+ cards: typeof cards;
+ collections: typeof collections;
+ http: typeof http;
+ scanHistory: typeof scanHistory;
+ users: typeof users;
+}>;
+
+/**
+ * A utility for referencing Convex functions in your app's public API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = api.myModule.myFunction;
+ * ```
+ */
+export declare const api: FilterApi<
+ typeof fullApi,
+ FunctionReference
+>;
+
+/**
+ * A utility for referencing Convex functions in your app's internal API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = internal.myModule.myFunction;
+ * ```
+ */
+export declare const internal: FilterApi<
+ typeof fullApi,
+ FunctionReference
+>;
+
+export declare const components: {};
diff --git a/convex/_generated/api.js b/convex/_generated/api.js
new file mode 100644
index 0000000..44bf985
--- /dev/null
+++ b/convex/_generated/api.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+/**
+ * Generated `api` utility.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import { anyApi, componentsGeneric } from "convex/server";
+
+/**
+ * A utility for referencing Convex functions in your app's API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = api.myModule.myFunction;
+ * ```
+ */
+export const api = anyApi;
+export const internal = anyApi;
+export const components = componentsGeneric();
diff --git a/convex/_generated/dataModel.d.ts b/convex/_generated/dataModel.d.ts
new file mode 100644
index 0000000..f97fd19
--- /dev/null
+++ b/convex/_generated/dataModel.d.ts
@@ -0,0 +1,60 @@
+/* eslint-disable */
+/**
+ * Generated data model types.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import type {
+ DataModelFromSchemaDefinition,
+ DocumentByName,
+ TableNamesInDataModel,
+ SystemTableNames,
+} from "convex/server";
+import type { GenericId } from "convex/values";
+import schema from "../schema.js";
+
+/**
+ * The names of all of your Convex tables.
+ */
+export type TableNames = TableNamesInDataModel;
+
+/**
+ * The type of a document stored in Convex.
+ *
+ * @typeParam TableName - A string literal type of the table name (like "users").
+ */
+export type Doc = DocumentByName<
+ DataModel,
+ TableName
+>;
+
+/**
+ * An identifier for a document in Convex.
+ *
+ * Convex documents are uniquely identified by their `Id`, which is accessible
+ * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
+ *
+ * Documents can be loaded using `db.get(tableName, id)` in query and mutation functions.
+ *
+ * IDs are just strings at runtime, but this type can be used to distinguish them from other
+ * strings when type checking.
+ *
+ * @typeParam TableName - A string literal type of the table name (like "users").
+ */
+export type Id =
+ GenericId;
+
+/**
+ * A type describing your Convex data model.
+ *
+ * This type includes information about what tables you have, the type of
+ * documents stored in those tables, and the indexes defined on them.
+ *
+ * This type is used to parameterize methods like `queryGeneric` and
+ * `mutationGeneric` to make them type-safe.
+ */
+export type DataModel = DataModelFromSchemaDefinition;
diff --git a/convex/_generated/server.d.ts b/convex/_generated/server.d.ts
new file mode 100644
index 0000000..bec05e6
--- /dev/null
+++ b/convex/_generated/server.d.ts
@@ -0,0 +1,143 @@
+/* eslint-disable */
+/**
+ * Generated utilities for implementing server-side Convex query and mutation functions.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import {
+ ActionBuilder,
+ HttpActionBuilder,
+ MutationBuilder,
+ QueryBuilder,
+ GenericActionCtx,
+ GenericMutationCtx,
+ GenericQueryCtx,
+ GenericDatabaseReader,
+ GenericDatabaseWriter,
+} from "convex/server";
+import type { DataModel } from "./dataModel.js";
+
+/**
+ * Define a query in this Convex app's public API.
+ *
+ * This function will be allowed to read your Convex database and will be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export declare const query: QueryBuilder;
+
+/**
+ * Define a query that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalQuery: QueryBuilder;
+
+/**
+ * Define a mutation in this Convex app's public API.
+ *
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export declare const mutation: MutationBuilder;
+
+/**
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalMutation: MutationBuilder;
+
+/**
+ * Define an action in this Convex app's public API.
+ *
+ * An action is a function which can execute any JavaScript code, including non-deterministic
+ * code and code with side-effects, like calling third-party services.
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
+ *
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
+ */
+export declare const action: ActionBuilder;
+
+/**
+ * Define an action that is only accessible from other Convex functions (but not from the client).
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalAction: ActionBuilder;
+
+/**
+ * Define an HTTP action.
+ *
+ * The wrapped function will be used to respond to HTTP requests received
+ * by a Convex deployment if the requests matches the path and method where
+ * this action is routed. Be sure to route your httpAction in `convex/http.js`.
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument
+ * and a Fetch API `Request` object as its second.
+ * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
+ */
+export declare const httpAction: HttpActionBuilder;
+
+/**
+ * A set of services for use within Convex query functions.
+ *
+ * The query context is passed as the first argument to any Convex query
+ * function run on the server.
+ *
+ * This differs from the {@link MutationCtx} because all of the services are
+ * read-only.
+ */
+export type QueryCtx = GenericQueryCtx;
+
+/**
+ * A set of services for use within Convex mutation functions.
+ *
+ * The mutation context is passed as the first argument to any Convex mutation
+ * function run on the server.
+ */
+export type MutationCtx = GenericMutationCtx;
+
+/**
+ * A set of services for use within Convex action functions.
+ *
+ * The action context is passed as the first argument to any Convex action
+ * function run on the server.
+ */
+export type ActionCtx = GenericActionCtx;
+
+/**
+ * An interface to read from the database within Convex query functions.
+ *
+ * The two entry points are {@link DatabaseReader.get}, which fetches a single
+ * document by its {@link Id}, or {@link DatabaseReader.query}, which starts
+ * building a query.
+ */
+export type DatabaseReader = GenericDatabaseReader;
+
+/**
+ * An interface to read from and write to the database within Convex mutation
+ * functions.
+ *
+ * Convex guarantees that all writes within a single mutation are
+ * executed atomically, so you never have to worry about partial writes leaving
+ * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
+ * for the guarantees Convex provides your functions.
+ */
+export type DatabaseWriter = GenericDatabaseWriter;
diff --git a/convex/_generated/server.js b/convex/_generated/server.js
new file mode 100644
index 0000000..bf3d25a
--- /dev/null
+++ b/convex/_generated/server.js
@@ -0,0 +1,93 @@
+/* eslint-disable */
+/**
+ * Generated utilities for implementing server-side Convex query and mutation functions.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import {
+ actionGeneric,
+ httpActionGeneric,
+ queryGeneric,
+ mutationGeneric,
+ internalActionGeneric,
+ internalMutationGeneric,
+ internalQueryGeneric,
+} from "convex/server";
+
+/**
+ * Define a query in this Convex app's public API.
+ *
+ * This function will be allowed to read your Convex database and will be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export const query = queryGeneric;
+
+/**
+ * Define a query that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export const internalQuery = internalQueryGeneric;
+
+/**
+ * Define a mutation in this Convex app's public API.
+ *
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export const mutation = mutationGeneric;
+
+/**
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export const internalMutation = internalMutationGeneric;
+
+/**
+ * Define an action in this Convex app's public API.
+ *
+ * An action is a function which can execute any JavaScript code, including non-deterministic
+ * code and code with side-effects, like calling third-party services.
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
+ *
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
+ */
+export const action = actionGeneric;
+
+/**
+ * Define an action that is only accessible from other Convex functions (but not from the client).
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
+ */
+export const internalAction = internalActionGeneric;
+
+/**
+ * Define an HTTP action.
+ *
+ * The wrapped function will be used to respond to HTTP requests received
+ * by a Convex deployment if the requests matches the path and method where
+ * this action is routed. Be sure to route your httpAction in `convex/http.js`.
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument
+ * and a Fetch API `Request` object as its second.
+ * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
+ */
+export const httpAction = httpActionGeneric;
diff --git a/convex/auth.ts b/convex/auth.ts
new file mode 100644
index 0000000..ea5f83e
--- /dev/null
+++ b/convex/auth.ts
@@ -0,0 +1,48 @@
+/**
+ * Convex Auth configuration with Zitadel OIDC.
+ *
+ * GDPR Compliance: No user profile data (name, email, image) is stored.
+ * User details must be fetched from Zitadel userinfo endpoint when needed.
+ */
+
+import Zitadel from "@auth/core/providers/zitadel";
+import { convexAuth } from "@convex-dev/auth/server";
+
+export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
+ providers: [
+ Zitadel({
+ issuer: process.env.AUTH_ZITADEL_ISSUER,
+ clientId: process.env.AUTH_ZITADEL_ID,
+ clientSecret: process.env.AUTH_ZITADEL_SECRET,
+ // Strip all profile data - return only the subject ID
+ profile(zitadelProfile) {
+ return {
+ id: zitadelProfile.sub,
+ // Intentionally omit: name, email, image
+ // These must be fetched from Zitadel userinfo endpoint
+ };
+ },
+ }),
+ ],
+ callbacks: {
+ // Validate redirect URIs for React Native
+ async redirect({ redirectTo }) {
+ const allowedPrefixes = [
+ "scry://", // App custom scheme
+ "app://", // Default Expo scheme
+ "exp://", // Expo Go
+ "http://localhost",
+ "https://localhost",
+ ];
+
+ const isAllowed = allowedPrefixes.some((prefix) =>
+ redirectTo.startsWith(prefix)
+ );
+
+ if (!isAllowed) {
+ throw new Error(`Invalid redirectTo URI: ${redirectTo}`);
+ }
+ return redirectTo;
+ },
+ },
+});
diff --git a/convex/cards.ts b/convex/cards.ts
new file mode 100644
index 0000000..0602608
--- /dev/null
+++ b/convex/cards.ts
@@ -0,0 +1,146 @@
+import { v } from "convex/values";
+import { query, mutation } from "./_generated/server";
+
+// Get all cards (for local caching)
+export const list = query({
+ args: {},
+ handler: async (ctx) => {
+ return await ctx.db.query("cards").collect();
+ },
+});
+
+// Get cards updated after a timestamp (for incremental sync)
+export const listUpdatedAfter = query({
+ args: { since: v.number() },
+ handler: async (ctx, { since }) => {
+ return await ctx.db
+ .query("cards")
+ .withIndex("by_updated", (q) => q.gt("updatedAt", since))
+ .collect();
+ },
+});
+
+// Get a single card by Scryfall ID
+export const getByScryfallId = query({
+ args: { scryfallId: v.string() },
+ handler: async (ctx, { scryfallId }) => {
+ return await ctx.db
+ .query("cards")
+ .withIndex("by_scryfall", (q) => q.eq("scryfallId", scryfallId))
+ .first();
+ },
+});
+
+// Get cards by oracle ID (all printings of a card)
+export const getByOracleId = query({
+ args: { oracleId: v.string() },
+ handler: async (ctx, { oracleId }) => {
+ return await ctx.db
+ .query("cards")
+ .withIndex("by_oracle", (q) => q.eq("oracleId", oracleId))
+ .collect();
+ },
+});
+
+// Search cards by name
+export const searchByName = query({
+ args: { name: v.string() },
+ handler: async (ctx, { name }) => {
+ const cards = await ctx.db.query("cards").collect();
+ const lowerName = name.toLowerCase();
+ return cards.filter((card) =>
+ card.name.toLowerCase().includes(lowerName)
+ );
+ },
+});
+
+// Get total card count
+export const count = query({
+ args: {},
+ handler: async (ctx) => {
+ const cards = await ctx.db.query("cards").collect();
+ return cards.length;
+ },
+});
+
+// Insert a card (used by migration script)
+export const insert = mutation({
+ args: {
+ scryfallId: v.string(),
+ oracleId: v.string(),
+ name: v.string(),
+ setCode: v.string(),
+ collectorNumber: v.string(),
+ rarity: v.string(),
+ artist: v.optional(v.string()),
+ imageUri: v.optional(v.string()),
+ hash: v.bytes(),
+ hashVersion: v.number(),
+ },
+ handler: async (ctx, args) => {
+ // Check if card already exists
+ const existing = await ctx.db
+ .query("cards")
+ .withIndex("by_scryfall", (q) => q.eq("scryfallId", args.scryfallId))
+ .first();
+
+ if (existing) {
+ // Update existing card
+ await ctx.db.patch(existing._id, {
+ ...args,
+ updatedAt: Date.now(),
+ });
+ return existing._id;
+ }
+
+ // Insert new card
+ return await ctx.db.insert("cards", {
+ ...args,
+ updatedAt: Date.now(),
+ });
+ },
+});
+
+// Batch insert cards
+export const insertBatch = mutation({
+ args: {
+ cards: v.array(
+ v.object({
+ scryfallId: v.string(),
+ oracleId: v.string(),
+ name: v.string(),
+ setCode: v.string(),
+ collectorNumber: v.string(),
+ rarity: v.string(),
+ artist: v.optional(v.string()),
+ imageUri: v.optional(v.string()),
+ hash: v.bytes(),
+ hashVersion: v.number(),
+ })
+ ),
+ },
+ handler: async (ctx, { cards }) => {
+ const results = [];
+ for (const card of cards) {
+ const existing = await ctx.db
+ .query("cards")
+ .withIndex("by_scryfall", (q) => q.eq("scryfallId", card.scryfallId))
+ .first();
+
+ if (existing) {
+ await ctx.db.patch(existing._id, {
+ ...card,
+ updatedAt: Date.now(),
+ });
+ results.push(existing._id);
+ } else {
+ const id = await ctx.db.insert("cards", {
+ ...card,
+ updatedAt: Date.now(),
+ });
+ results.push(id);
+ }
+ }
+ return results;
+ },
+});
diff --git a/convex/collections.ts b/convex/collections.ts
new file mode 100644
index 0000000..670ae04
--- /dev/null
+++ b/convex/collections.ts
@@ -0,0 +1,166 @@
+import { v } from "convex/values";
+import { query, mutation } from "./_generated/server";
+
+// Get user's collection (alias for backwards compat)
+export const getByUser = query({
+ args: { userId: v.id("users") },
+ handler: async (ctx, { userId }) => {
+ const entries = await ctx.db
+ .query("collections")
+ .withIndex("by_user", (q) => q.eq("userId", userId))
+ .collect();
+
+ // Enrich with card data
+ const enriched = await Promise.all(
+ entries.map(async (entry) => {
+ const card = await ctx.db.get(entry.cardId);
+ return {
+ ...entry,
+ card,
+ };
+ })
+ );
+
+ return enriched.filter((e) => e.card !== null);
+ },
+});
+
+// Get user's collection
+export const getMyCollection = query({
+ args: { userId: v.id("users") },
+ handler: async (ctx, { userId }) => {
+ const entries = await ctx.db
+ .query("collections")
+ .withIndex("by_user", (q) => q.eq("userId", userId))
+ .collect();
+
+ // Enrich with card data
+ const enriched = await Promise.all(
+ entries.map(async (entry) => {
+ const card = await ctx.db.get(entry.cardId);
+ return {
+ ...entry,
+ card,
+ };
+ })
+ );
+
+ return enriched.filter((e) => e.card !== null);
+ },
+});
+
+// Get collection stats
+export const getStats = query({
+ args: { userId: v.id("users") },
+ handler: async (ctx, { userId }) => {
+ const entries = await ctx.db
+ .query("collections")
+ .withIndex("by_user", (q) => q.eq("userId", userId))
+ .collect();
+
+ const totalCards = entries.reduce((sum, e) => sum + e.quantity, 0);
+ const uniqueCards = entries.length;
+ const foilCount = entries.filter((e) => e.isFoil).length;
+
+ return {
+ totalCards,
+ uniqueCards,
+ foilCount,
+ };
+ },
+});
+
+// Add card to collection
+export const addCard = mutation({
+ args: {
+ userId: v.id("users"),
+ cardId: v.id("cards"),
+ quantity: v.number(),
+ isFoil: v.boolean(),
+ },
+ handler: async (ctx, { userId, cardId, quantity, isFoil }) => {
+ // Check if entry already exists
+ const existing = await ctx.db
+ .query("collections")
+ .withIndex("by_user_card", (q) =>
+ q.eq("userId", userId).eq("cardId", cardId).eq("isFoil", isFoil)
+ )
+ .first();
+
+ if (existing) {
+ // Update quantity
+ await ctx.db.patch(existing._id, {
+ quantity: existing.quantity + quantity,
+ });
+ return existing._id;
+ }
+
+ // Create new entry
+ return await ctx.db.insert("collections", {
+ userId,
+ cardId,
+ quantity,
+ isFoil,
+ addedAt: Date.now(),
+ });
+ },
+});
+
+// Alias for addCard
+export const add = addCard;
+
+// Update card quantity
+export const updateQuantity = mutation({
+ args: {
+ entryId: v.id("collections"),
+ quantity: v.number(),
+ },
+ handler: async (ctx, { entryId, quantity }) => {
+ if (quantity <= 0) {
+ await ctx.db.delete(entryId);
+ return null;
+ }
+ await ctx.db.patch(entryId, { quantity });
+ return entryId;
+ },
+});
+
+// Remove card from collection
+export const removeCard = mutation({
+ args: { entryId: v.id("collections") },
+ handler: async (ctx, { entryId }) => {
+ await ctx.db.delete(entryId);
+ },
+});
+
+// Alias for removeCard
+export const remove = removeCard;
+
+// Decrease quantity by 1
+export const decrementQuantity = mutation({
+ args: { entryId: v.id("collections") },
+ handler: async (ctx, { entryId }) => {
+ const entry = await ctx.db.get(entryId);
+ if (!entry) return null;
+
+ if (entry.quantity <= 1) {
+ await ctx.db.delete(entryId);
+ return null;
+ }
+
+ await ctx.db.patch(entryId, { quantity: entry.quantity - 1 });
+ return entryId;
+ },
+});
+
+// Increase quantity by 1
+export const incrementQuantity = mutation({
+ args: { entryId: v.id("collections") },
+ handler: async (ctx, { entryId }) => {
+ const entry = await ctx.db.get(entryId);
+ if (!entry) return null;
+
+ await ctx.db.patch(entryId, { quantity: entry.quantity + 1 });
+ return entryId;
+ },
+});
diff --git a/convex/http.ts b/convex/http.ts
new file mode 100644
index 0000000..8d08c29
--- /dev/null
+++ b/convex/http.ts
@@ -0,0 +1,13 @@
+/**
+ * HTTP routes for Convex Auth.
+ */
+
+import { httpRouter } from "convex/server";
+import { auth } from "./auth";
+
+const http = httpRouter();
+
+// Add Convex Auth routes
+auth.addHttpRoutes(http);
+
+export default http;
diff --git a/convex/scanHistory.ts b/convex/scanHistory.ts
new file mode 100644
index 0000000..5f29fa4
--- /dev/null
+++ b/convex/scanHistory.ts
@@ -0,0 +1,58 @@
+import { v } from "convex/values";
+import { query, mutation } from "./_generated/server";
+
+// Get recent scan history
+export const getRecent = query({
+ args: { userId: v.id("users"), limit: v.optional(v.number()) },
+ handler: async (ctx, { userId, limit = 50 }) => {
+ const history = await ctx.db
+ .query("scanHistory")
+ .withIndex("by_user", (q) => q.eq("userId", userId))
+ .order("desc")
+ .take(limit);
+
+ // Enrich with card data
+ const enriched = await Promise.all(
+ history.map(async (entry) => {
+ const card = entry.cardId ? await ctx.db.get(entry.cardId) : null;
+ return {
+ ...entry,
+ card,
+ };
+ })
+ );
+
+ return enriched;
+ },
+});
+
+// Record a scan
+export const record = mutation({
+ args: {
+ userId: v.id("users"),
+ cardId: v.optional(v.id("cards")),
+ confidence: v.number(),
+ addedToCollection: v.boolean(),
+ },
+ handler: async (ctx, args) => {
+ return await ctx.db.insert("scanHistory", {
+ ...args,
+ scannedAt: Date.now(),
+ });
+ },
+});
+
+// Clear scan history
+export const clear = mutation({
+ args: { userId: v.id("users") },
+ handler: async (ctx, { userId }) => {
+ const entries = await ctx.db
+ .query("scanHistory")
+ .withIndex("by_user", (q) => q.eq("userId", userId))
+ .collect();
+
+ for (const entry of entries) {
+ await ctx.db.delete(entry._id);
+ }
+ },
+});
diff --git a/convex/schema.ts b/convex/schema.ts
new file mode 100644
index 0000000..4012ecb
--- /dev/null
+++ b/convex/schema.ts
@@ -0,0 +1,93 @@
+import { defineSchema, defineTable } from "convex/server";
+import { v } from "convex/values";
+import { authTables } from "@convex-dev/auth/server";
+
+// Override the default users table to store NO personal data (GDPR compliance)
+// User profile info (name, email, etc.) must be fetched from Zitadel OIDC userinfo endpoint
+const minimalAuthTables = {
+ ...authTables,
+ // Override users table - only store what's required for auth to function
+ users: defineTable({
+ // No name, email, image, or any PII stored
+ // The auth system needs this table to exist but we strip all profile data
+ }),
+};
+
+export default defineSchema({
+ ...minimalAuthTables,
+
+ // Card printings with perceptual hashes
+ cards: defineTable({
+ scryfallId: v.string(),
+ oracleId: v.string(),
+ name: v.string(),
+ setCode: v.string(),
+ collectorNumber: v.string(),
+ rarity: v.string(),
+ artist: v.optional(v.string()),
+ imageUri: v.optional(v.string()),
+ hash: v.bytes(), // 24-byte perceptual hash
+ hashVersion: v.number(), // Algorithm version for migrations
+ updatedAt: v.number(),
+ })
+ .index("by_scryfall", ["scryfallId"])
+ .index("by_oracle", ["oracleId"])
+ .index("by_name", ["name"])
+ .index("by_updated", ["updatedAt"]),
+
+ // Oracle cards (abstract game cards)
+ oracles: defineTable({
+ oracleId: v.string(),
+ name: v.string(),
+ manaCost: v.optional(v.string()),
+ cmc: v.optional(v.number()),
+ typeLine: v.optional(v.string()),
+ oracleText: v.optional(v.string()),
+ colors: v.optional(v.array(v.string())),
+ colorIdentity: v.optional(v.array(v.string())),
+ keywords: v.optional(v.array(v.string())),
+ power: v.optional(v.string()),
+ toughness: v.optional(v.string()),
+ })
+ .index("by_oracle", ["oracleId"])
+ .index("by_name", ["name"]),
+
+ // MTG sets
+ sets: defineTable({
+ setId: v.string(),
+ code: v.string(),
+ name: v.string(),
+ setType: v.optional(v.string()),
+ releasedAt: v.optional(v.string()),
+ cardCount: v.optional(v.number()),
+ iconSvgUri: v.optional(v.string()),
+ })
+ .index("by_set", ["setId"])
+ .index("by_code", ["code"]),
+
+ // User collections
+ collections: defineTable({
+ userId: v.id("users"),
+ cardId: v.id("cards"),
+ quantity: v.number(),
+ isFoil: v.boolean(),
+ addedAt: v.number(),
+ })
+ .index("by_user", ["userId"])
+ .index("by_user_card", ["userId", "cardId", "isFoil"]),
+
+ // Scan history
+ scanHistory: defineTable({
+ userId: v.id("users"),
+ cardId: v.optional(v.id("cards")),
+ confidence: v.number(),
+ scannedAt: v.number(),
+ addedToCollection: v.boolean(),
+ }).index("by_user", ["userId"]),
+
+ // Sync metadata
+ metadata: defineTable({
+ key: v.string(),
+ value: v.string(),
+ }).index("by_key", ["key"]),
+});
diff --git a/convex/users.ts b/convex/users.ts
new file mode 100644
index 0000000..10aca10
--- /dev/null
+++ b/convex/users.ts
@@ -0,0 +1,20 @@
+/**
+ * User queries and mutations using Convex Auth.
+ */
+
+import { query } from "./_generated/server";
+import { getAuthUserId } from "@convex-dev/auth/server";
+
+/**
+ * Get the currently authenticated user.
+ */
+export const me = query({
+ args: {},
+ handler: async (ctx) => {
+ const userId = await getAuthUserId(ctx);
+ if (!userId) {
+ return null;
+ }
+ return await ctx.db.get(userId);
+ },
+});
diff --git a/lib/context/HashCacheContext.tsx b/lib/context/HashCacheContext.tsx
new file mode 100644
index 0000000..3b4ed0d
--- /dev/null
+++ b/lib/context/HashCacheContext.tsx
@@ -0,0 +1,64 @@
+/**
+ * React Context for card hash cache.
+ * Provides offline-first hash data for card recognition.
+ */
+
+import React, { createContext, useContext, useState, useCallback, type ReactNode } from "react";
+import type { CardHashEntry } from "../recognition";
+
+interface HashCacheState {
+ cardHashes: CardHashEntry[];
+ hashesLoaded: boolean;
+ lastHashSync: number;
+}
+
+interface HashCacheContextValue extends HashCacheState {
+ setCardHashes: (hashes: CardHashEntry[]) => void;
+ clearHashes: () => void;
+}
+
+const HashCacheContext = createContext(null);
+
+export function HashCacheProvider({ children }: { children: ReactNode }) {
+ const [state, setState] = useState({
+ cardHashes: [],
+ hashesLoaded: false,
+ lastHashSync: 0,
+ });
+
+ const setCardHashes = useCallback((hashes: CardHashEntry[]) => {
+ setState({
+ cardHashes: hashes,
+ hashesLoaded: true,
+ lastHashSync: Date.now(),
+ });
+ }, []);
+
+ const clearHashes = useCallback(() => {
+ setState({
+ cardHashes: [],
+ hashesLoaded: false,
+ lastHashSync: 0,
+ });
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useHashCache() {
+ const context = useContext(HashCacheContext);
+ if (!context) {
+ throw new Error("useHashCache must be used within HashCacheProvider");
+ }
+ return context;
+}
diff --git a/lib/context/index.ts b/lib/context/index.ts
new file mode 100644
index 0000000..449c105
--- /dev/null
+++ b/lib/context/index.ts
@@ -0,0 +1,5 @@
+/**
+ * Context exports.
+ */
+
+export { HashCacheProvider, useHashCache } from "./HashCacheContext";
diff --git a/lib/db/index.ts b/lib/db/index.ts
new file mode 100644
index 0000000..341dd18
--- /dev/null
+++ b/lib/db/index.ts
@@ -0,0 +1,20 @@
+/**
+ * Local database module exports.
+ */
+
+export {
+ initDatabase,
+ getLastSyncTimestamp,
+ setLastSyncTimestamp,
+ getCachedHashes,
+ getCachedCardCount,
+ upsertCards,
+ clearCache,
+ closeDatabase,
+} from "./localDatabase";
+
+export {
+ createSyncService,
+ type SyncService,
+ type SyncStatus,
+} from "./syncService";
diff --git a/lib/db/localDatabase.ts b/lib/db/localDatabase.ts
new file mode 100644
index 0000000..9523892
--- /dev/null
+++ b/lib/db/localDatabase.ts
@@ -0,0 +1,180 @@
+/**
+ * Local SQLite database for offline card hash caching.
+ * Syncs with Convex and provides offline recognition support.
+ */
+
+import * as SQLite from "expo-sqlite";
+import type { CardHashEntry } from "../recognition";
+
+const DB_NAME = "scry_cache.db";
+
+let db: SQLite.SQLiteDatabase | null = null;
+
+/**
+ * Initialize the local database.
+ */
+export async function initDatabase(): Promise {
+ if (db) return;
+
+ db = await SQLite.openDatabaseAsync(DB_NAME);
+
+ // Create tables
+ await db.execAsync(`
+ CREATE TABLE IF NOT EXISTS cards (
+ scryfall_id TEXT PRIMARY KEY,
+ oracle_id TEXT NOT NULL,
+ name TEXT NOT NULL,
+ set_code TEXT NOT NULL,
+ collector_number TEXT,
+ rarity TEXT,
+ artist TEXT,
+ image_uri TEXT,
+ hash BLOB NOT NULL,
+ hash_version INTEGER DEFAULT 1,
+ updated_at INTEGER NOT NULL
+ );
+
+ CREATE TABLE IF NOT EXISTS sync_metadata (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_cards_name ON cards(name);
+ CREATE INDEX IF NOT EXISTS idx_cards_updated ON cards(updated_at);
+ `);
+
+ console.log("[LocalDB] Database initialized");
+}
+
+/**
+ * Get the last sync timestamp.
+ */
+export async function getLastSyncTimestamp(): Promise {
+ if (!db) await initDatabase();
+
+ const result = await db!.getFirstAsync<{ value: string }>(
+ "SELECT value FROM sync_metadata WHERE key = ?",
+ ["last_sync"]
+ );
+
+ return result ? parseInt(result.value, 10) : 0;
+}
+
+/**
+ * Set the last sync timestamp.
+ */
+export async function setLastSyncTimestamp(timestamp: number): Promise {
+ if (!db) await initDatabase();
+
+ await db!.runAsync(
+ "INSERT OR REPLACE INTO sync_metadata (key, value) VALUES (?, ?)",
+ ["last_sync", timestamp.toString()]
+ );
+}
+
+/**
+ * Get all cached card hashes for recognition.
+ */
+export async function getCachedHashes(): Promise {
+ if (!db) await initDatabase();
+
+ const rows = await db!.getAllAsync<{
+ scryfall_id: string;
+ name: string;
+ set_code: string;
+ collector_number: string | null;
+ image_uri: string | null;
+ hash: Uint8Array;
+ }>("SELECT scryfall_id, name, set_code, collector_number, image_uri, hash FROM cards");
+
+ return rows.map((row) => ({
+ id: row.scryfall_id,
+ name: row.name,
+ setCode: row.set_code,
+ collectorNumber: row.collector_number || undefined,
+ imageUri: row.image_uri || undefined,
+ hash: new Uint8Array(row.hash),
+ }));
+}
+
+/**
+ * Get the count of cached cards.
+ */
+export async function getCachedCardCount(): Promise {
+ if (!db) await initDatabase();
+
+ const result = await db!.getFirstAsync<{ count: number }>(
+ "SELECT COUNT(*) as count FROM cards"
+ );
+
+ return result?.count || 0;
+}
+
+/**
+ * Insert or update cards from Convex.
+ */
+export async function upsertCards(cards: Array<{
+ scryfallId: string;
+ oracleId: string;
+ name: string;
+ setCode: string;
+ collectorNumber: string;
+ rarity: string;
+ artist?: string;
+ imageUri?: string;
+ hash: Uint8Array;
+ hashVersion: number;
+ updatedAt: number;
+}>): Promise {
+ if (!db) await initDatabase();
+
+ // Use a transaction for batch insert
+ await db!.withTransactionAsync(async () => {
+ for (const card of cards) {
+ await db!.runAsync(
+ `INSERT OR REPLACE INTO cards
+ (scryfall_id, oracle_id, name, set_code, collector_number, rarity, artist, image_uri, hash, hash_version, updated_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ [
+ card.scryfallId,
+ card.oracleId,
+ card.name,
+ card.setCode,
+ card.collectorNumber,
+ card.rarity,
+ card.artist || null,
+ card.imageUri || null,
+ card.hash,
+ card.hashVersion,
+ card.updatedAt,
+ ]
+ );
+ }
+ });
+
+ console.log(`[LocalDB] Upserted ${cards.length} cards`);
+}
+
+/**
+ * Clear all cached data.
+ */
+export async function clearCache(): Promise {
+ if (!db) await initDatabase();
+
+ await db!.execAsync(`
+ DELETE FROM cards;
+ DELETE FROM sync_metadata;
+ `);
+
+ console.log("[LocalDB] Cache cleared");
+}
+
+/**
+ * Close the database connection.
+ */
+export async function closeDatabase(): Promise {
+ if (db) {
+ await db.closeAsync();
+ db = null;
+ }
+}
diff --git a/lib/db/syncService.ts b/lib/db/syncService.ts
new file mode 100644
index 0000000..7892961
--- /dev/null
+++ b/lib/db/syncService.ts
@@ -0,0 +1,183 @@
+/**
+ * Sync service for keeping local SQLite cache in sync with Convex.
+ * Provides offline-first functionality with background sync.
+ */
+
+import { ConvexReactClient } from "convex/react";
+import { api } from "../../convex/_generated/api";
+import {
+ initDatabase,
+ getLastSyncTimestamp,
+ setLastSyncTimestamp,
+ getCachedHashes,
+ getCachedCardCount,
+ upsertCards,
+ clearCache,
+} from "./localDatabase";
+import type { CardHashEntry } from "../recognition";
+
+export interface SyncStatus {
+ isInitialized: boolean;
+ isSyncing: boolean;
+ lastSync: number;
+ localCardCount: number;
+ error?: string;
+}
+
+export interface SyncService {
+ initialize: () => Promise;
+ sync: () => Promise;
+ getHashes: () => Promise;
+ getStatus: () => SyncStatus;
+ clearLocalCache: () => Promise;
+ onStatusChange: (callback: (status: SyncStatus) => void) => () => void;
+}
+
+/**
+ * Create a sync service instance.
+ */
+export function createSyncService(convexClient: ConvexReactClient): SyncService {
+ let status: SyncStatus = {
+ isInitialized: false,
+ isSyncing: false,
+ lastSync: 0,
+ localCardCount: 0,
+ };
+
+ const listeners = new Set<(status: SyncStatus) => void>();
+
+ function notifyListeners() {
+ listeners.forEach((cb) => cb(status));
+ }
+
+ function updateStatus(partial: Partial) {
+ status = { ...status, ...partial };
+ notifyListeners();
+ }
+
+ return {
+ /**
+ * Initialize the local database and load cached data.
+ */
+ async initialize() {
+ try {
+ await initDatabase();
+
+ const [lastSync, cardCount] = await Promise.all([
+ getLastSyncTimestamp(),
+ getCachedCardCount(),
+ ]);
+
+ updateStatus({
+ isInitialized: true,
+ lastSync,
+ localCardCount: cardCount,
+ error: undefined,
+ });
+
+ console.log(`[SyncService] Initialized with ${cardCount} cached cards`);
+ } catch (error) {
+ console.error("[SyncService] Initialization failed:", error);
+ updateStatus({
+ error: error instanceof Error ? error.message : "Initialization failed",
+ });
+ }
+ },
+
+ /**
+ * Sync with Convex - fetch new/updated cards.
+ */
+ async sync() {
+ if (status.isSyncing) {
+ console.log("[SyncService] Sync already in progress");
+ return;
+ }
+
+ updateStatus({ isSyncing: true, error: undefined });
+
+ try {
+ // Get cards updated since last sync
+ const lastSync = status.lastSync;
+
+ // Query Convex for updated cards
+ // Note: This uses the HTTP client for one-off queries
+ const cards = lastSync > 0
+ ? await convexClient.query(api.cards.listUpdatedAfter, { since: lastSync })
+ : await convexClient.query(api.cards.list, {});
+
+ if (cards && cards.length > 0) {
+ // Convert to local format and save
+ const localCards = cards.map((card) => ({
+ scryfallId: card.scryfallId,
+ oracleId: card.oracleId,
+ name: card.name,
+ setCode: card.setCode,
+ collectorNumber: card.collectorNumber,
+ rarity: card.rarity,
+ artist: card.artist,
+ imageUri: card.imageUri,
+ hash: new Uint8Array(card.hash),
+ hashVersion: card.hashVersion,
+ updatedAt: card.updatedAt,
+ }));
+
+ await upsertCards(localCards);
+ console.log(`[SyncService] Synced ${localCards.length} cards`);
+ }
+
+ // Update sync timestamp
+ const now = Date.now();
+ await setLastSyncTimestamp(now);
+
+ // Update status
+ const cardCount = await getCachedCardCount();
+ updateStatus({
+ isSyncing: false,
+ lastSync: now,
+ localCardCount: cardCount,
+ });
+
+ console.log(`[SyncService] Sync complete. ${cardCount} cards cached.`);
+ } catch (error) {
+ console.error("[SyncService] Sync failed:", error);
+ updateStatus({
+ isSyncing: false,
+ error: error instanceof Error ? error.message : "Sync failed",
+ });
+ }
+ },
+
+ /**
+ * Get cached hashes for recognition.
+ */
+ async getHashes() {
+ return getCachedHashes();
+ },
+
+ /**
+ * Get current sync status.
+ */
+ getStatus() {
+ return status;
+ },
+
+ /**
+ * Clear local cache and re-sync.
+ */
+ async clearLocalCache() {
+ await clearCache();
+ updateStatus({
+ lastSync: 0,
+ localCardCount: 0,
+ });
+ },
+
+ /**
+ * Subscribe to status changes.
+ */
+ onStatusChange(callback: (status: SyncStatus) => void) {
+ listeners.add(callback);
+ return () => listeners.delete(callback);
+ },
+ };
+}
diff --git a/lib/hooks/index.ts b/lib/hooks/index.ts
new file mode 100644
index 0000000..4d7107b
--- /dev/null
+++ b/lib/hooks/index.ts
@@ -0,0 +1,9 @@
+/**
+ * Hooks module exports.
+ */
+
+export { useCameraPermission } from "./useCamera";
+export { useCardHashes, useCardCount, useSearchCards, useCollection, useCurrentUser } from "./useConvex";
+export { useSync, SyncInitializer } from "./useSync";
+export { useAuth } from "./useAuth";
+export { useUserProfile, storeAccessToken, clearAccessToken } from "./useUserProfile";
diff --git a/lib/hooks/useAuth.ts b/lib/hooks/useAuth.ts
new file mode 100644
index 0000000..9bf8dca
--- /dev/null
+++ b/lib/hooks/useAuth.ts
@@ -0,0 +1,112 @@
+/**
+ * Authentication hook and utilities.
+ *
+ * NOTE: Auth is currently disabled (using ConvexProvider instead of ConvexAuthProvider).
+ * This hook provides a stub implementation until Zitadel is configured.
+ */
+
+import { useState, useCallback } from "react";
+
+/**
+ * Hook for authentication state and actions.
+ * Currently returns unauthenticated state - enable ConvexAuthProvider when Zitadel is ready.
+ */
+export function useAuth() {
+ const [error, setError] = useState(null);
+
+ const handleSignIn = useCallback(async () => {
+ setError("Authentication not configured. Set up Zitadel environment variables.");
+ }, []);
+
+ const handleSignOut = useCallback(async () => {
+ // No-op when not authenticated
+ }, []);
+
+ return {
+ isAuthenticated: false,
+ isLoading: false,
+ error,
+ signIn: handleSignIn,
+ signOut: handleSignOut,
+ clearError: () => setError(null),
+ };
+}
+
+// Full implementation for when ConvexAuthProvider is enabled:
+/*
+import { useConvexAuth } from "convex/react";
+import { useAuthActions } from "@convex-dev/auth/react";
+import { makeRedirectUri } from "expo-auth-session";
+import { openAuthSessionAsync } from "expo-web-browser";
+import { Platform } from "react-native";
+
+const redirectUri = makeRedirectUri({
+ scheme: "scry",
+ path: "auth",
+});
+
+export function useAuth() {
+ const { isAuthenticated, isLoading } = useConvexAuth();
+ const { signIn, signOut } = useAuthActions();
+ const [isSigningIn, setIsSigningIn] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleSignIn = useCallback(async () => {
+ setIsSigningIn(true);
+ setError(null);
+
+ try {
+ const result = await signIn("zitadel", { redirectTo: redirectUri });
+
+ if (Platform.OS === "web") {
+ return;
+ }
+
+ if (result?.redirect) {
+ const authResult = await openAuthSessionAsync(
+ result.redirect.toString(),
+ redirectUri,
+ {
+ showInRecents: true,
+ preferEphemeralSession: false,
+ }
+ );
+
+ if (authResult.type === "success") {
+ const url = new URL(authResult.url);
+ const code = url.searchParams.get("code");
+
+ if (code) {
+ await signIn("zitadel", { code });
+ }
+ } else if (authResult.type === "cancel") {
+ setError("Sign-in was cancelled");
+ }
+ }
+ } catch (err) {
+ console.error("Sign-in error:", err);
+ setError(err instanceof Error ? err.message : "Sign-in failed");
+ } finally {
+ setIsSigningIn(false);
+ }
+ }, [signIn]);
+
+ const handleSignOut = useCallback(async () => {
+ try {
+ await signOut();
+ } catch (err) {
+ console.error("Sign-out error:", err);
+ setError(err instanceof Error ? err.message : "Sign-out failed");
+ }
+ }, [signOut]);
+
+ return {
+ isAuthenticated,
+ isLoading: isLoading || isSigningIn,
+ error,
+ signIn: handleSignIn,
+ signOut: handleSignOut,
+ clearError: () => setError(null),
+ };
+}
+*/
diff --git a/lib/hooks/useCamera.ts b/lib/hooks/useCamera.ts
new file mode 100644
index 0000000..219af58
--- /dev/null
+++ b/lib/hooks/useCamera.ts
@@ -0,0 +1,116 @@
+/**
+ * Hook for camera permissions.
+ * Supports both expo-camera (Expo Go) and react-native-vision-camera (production).
+ */
+
+import { useState, useEffect, useRef } from "react";
+import { Platform, Linking, Alert } from "react-native";
+import Constants from "expo-constants";
+
+// Detect if running in Expo Go
+const isExpoGo = Constants.appOwnership === "expo";
+
+export interface CameraPermissionState {
+ hasPermission: boolean;
+ isLoading: boolean;
+ error: string | null;
+}
+
+/**
+ * Hook for camera permissions that works in both Expo Go and production builds.
+ */
+export function useCameraPermission(): CameraPermissionState & { requestPermission: () => Promise } {
+ const [hasPermission, setHasPermission] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ checkPermission();
+ }, []);
+
+ const checkPermission = async () => {
+ try {
+ if (isExpoGo) {
+ // Use expo-camera
+ const { Camera } = await import("expo-camera");
+ const { status } = await Camera.getCameraPermissionsAsync();
+ setHasPermission(status === "granted");
+ } else {
+ // Use react-native-vision-camera
+ const { Camera } = await import("react-native-vision-camera");
+ const status = await Camera.getCameraPermissionStatus();
+ setHasPermission(status === "granted");
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to check camera permission");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const requestPermission = async () => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ if (isExpoGo) {
+ // Use expo-camera
+ const { Camera } = await import("expo-camera");
+ const { status, canAskAgain } = await Camera.requestCameraPermissionsAsync();
+
+ if (status === "granted") {
+ setHasPermission(true);
+ } else {
+ if (!canAskAgain) {
+ showSettingsAlert();
+ }
+ setError("Camera permission denied");
+ }
+ } else {
+ // Use react-native-vision-camera
+ const { Camera } = await import("react-native-vision-camera");
+ const status = await Camera.requestCameraPermission();
+
+ if (status === "granted") {
+ setHasPermission(true);
+ } else if (status === "denied") {
+ showSettingsAlert();
+ setError("Camera permission denied");
+ }
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to request camera permission");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return { hasPermission, isLoading, error, requestPermission };
+}
+
+function showSettingsAlert() {
+ Alert.alert(
+ "Camera Permission Required",
+ "Scry needs camera access to scan cards. Please enable it in Settings.",
+ [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Open Settings",
+ onPress: () => {
+ if (Platform.OS === "ios") {
+ Linking.openURL("app-settings:");
+ } else {
+ Linking.openSettings();
+ }
+ },
+ },
+ ]
+ );
+}
+
+/**
+ * Returns true if using expo-camera (Expo Go), false for Vision Camera (production).
+ */
+export function useIsExpoGo(): boolean {
+ return isExpoGo;
+}
diff --git a/lib/hooks/useConvex.ts b/lib/hooks/useConvex.ts
new file mode 100644
index 0000000..184208a
--- /dev/null
+++ b/lib/hooks/useConvex.ts
@@ -0,0 +1,101 @@
+/**
+ * Hooks for Convex data access.
+ */
+
+import { useQuery, useMutation } from "convex/react";
+import { api } from "../../convex/_generated/api";
+import { useHashCache } from "../context";
+import { useEffect } from "react";
+import type { CardHashEntry } from "../recognition";
+import type { Id } from "../../convex/_generated/dataModel";
+
+type User = { _id: Id<"users"> } | null | undefined;
+
+/**
+ * Hook to get the current authenticated user.
+ * Returns unauthenticated state when auth is not configured.
+ */
+export function useCurrentUser(): { user: User; isLoading: boolean; isAuthenticated: boolean } {
+ // When using ConvexProvider (no auth), just return unauthenticated state
+ // Switch to ConvexAuthProvider + useConvexAuth when Zitadel is configured
+ return {
+ user: null,
+ isLoading: false,
+ isAuthenticated: false,
+ };
+}
+
+/**
+ * Hook to fetch and cache card hashes from Convex.
+ */
+export function useCardHashes() {
+ const { setCardHashes, hashesLoaded, lastHashSync, cardHashes } = useHashCache();
+
+ // Fetch all cards from Convex
+ const cards = useQuery(api.cards.list);
+
+ useEffect(() => {
+ if (cards) {
+ // Convert Convex cards to CardHashEntry format
+ const entries: CardHashEntry[] = cards.map((card) => ({
+ id: card.scryfallId,
+ name: card.name,
+ setCode: card.setCode,
+ collectorNumber: card.collectorNumber,
+ imageUri: card.imageUri,
+ hash: new Uint8Array(card.hash),
+ }));
+
+ setCardHashes(entries);
+ }
+ }, [cards, setCardHashes]);
+
+ return {
+ loading: cards === undefined && !hashesLoaded,
+ loaded: hashesLoaded,
+ count: cardHashes.length,
+ lastSync: lastHashSync,
+ hashes: cardHashes,
+ };
+}
+
+/**
+ * Hook to get card count.
+ */
+export function useCardCount() {
+ const count = useQuery(api.cards.count);
+ return count ?? 0;
+}
+
+/**
+ * Hook to search cards by name.
+ */
+export function useSearchCards(query: string) {
+ const results = useQuery(
+ api.cards.searchByName,
+ query.length >= 2 ? { name: query } : "skip"
+ );
+ return results ?? [];
+}
+
+/**
+ * Hook to manage collection.
+ */
+export function useCollection(userId?: string) {
+ const collection = useQuery(
+ api.collections.getByUser,
+ userId ? { userId: userId as any } : "skip"
+ );
+
+ const addCard = useMutation(api.collections.add);
+ const removeCard = useMutation(api.collections.remove);
+ const updateQuantity = useMutation(api.collections.updateQuantity);
+
+ return {
+ collection: collection ?? [],
+ addCard,
+ removeCard,
+ updateQuantity,
+ loading: collection === undefined,
+ };
+}
diff --git a/lib/hooks/useSync.ts b/lib/hooks/useSync.ts
new file mode 100644
index 0000000..05f24bd
--- /dev/null
+++ b/lib/hooks/useSync.ts
@@ -0,0 +1,102 @@
+/**
+ * Hook for managing offline sync with local SQLite cache.
+ */
+
+import { useEffect, useState, useCallback, useRef } from "react";
+import { useConvex } from "convex/react";
+import { createSyncService, type SyncService, type SyncStatus } from "../db/syncService";
+import { useHashCache } from "../context";
+
+/**
+ * Hook to manage sync service lifecycle and status.
+ * Initializes local database, syncs with Convex, and provides hashes for recognition.
+ */
+export function useSync() {
+ const convex = useConvex();
+ const syncServiceRef = useRef(null);
+ const [status, setStatus] = useState({
+ isInitialized: false,
+ isSyncing: false,
+ lastSync: 0,
+ localCardCount: 0,
+ });
+
+ const { setCardHashes } = useHashCache();
+
+ // Initialize sync service
+ useEffect(() => {
+ if (!convex || syncServiceRef.current) return;
+
+ const service = createSyncService(convex);
+ syncServiceRef.current = service;
+
+ // Subscribe to status changes
+ const unsubscribe = service.onStatusChange(setStatus);
+
+ // Initialize and load cached hashes
+ const init = async () => {
+ await service.initialize();
+
+ // Load cached hashes into app store
+ const hashes = await service.getHashes();
+ if (hashes.length > 0) {
+ setCardHashes(hashes);
+ console.log(`[useSync] Loaded ${hashes.length} cached hashes`);
+ }
+
+ // Start background sync
+ service.sync().then(async () => {
+ // Reload hashes after sync
+ const updatedHashes = await service.getHashes();
+ setCardHashes(updatedHashes);
+ });
+ };
+
+ init();
+
+ return () => {
+ unsubscribe();
+ };
+ }, [convex, setCardHashes]);
+
+ // Manual sync trigger
+ const sync = useCallback(async () => {
+ if (!syncServiceRef.current) return;
+
+ await syncServiceRef.current.sync();
+
+ // Reload hashes after sync
+ const hashes = await syncServiceRef.current.getHashes();
+ setCardHashes(hashes);
+ }, [setCardHashes]);
+
+ // Clear local cache
+ const clearCache = useCallback(async () => {
+ if (!syncServiceRef.current) return;
+
+ await syncServiceRef.current.clearLocalCache();
+ setCardHashes([]);
+ }, [setCardHashes]);
+
+ // Get current hashes from cache
+ const getHashes = useCallback(async () => {
+ if (!syncServiceRef.current) return [];
+ return syncServiceRef.current.getHashes();
+ }, []);
+
+ return {
+ ...status,
+ sync,
+ clearCache,
+ getHashes,
+ };
+}
+
+/**
+ * Context-free sync initialization for use in _layout.tsx
+ * Returns a component that initializes sync when mounted.
+ */
+export function SyncInitializer() {
+ useSync();
+ return null;
+}
diff --git a/lib/hooks/useUserProfile.ts b/lib/hooks/useUserProfile.ts
new file mode 100644
index 0000000..920af9d
--- /dev/null
+++ b/lib/hooks/useUserProfile.ts
@@ -0,0 +1,115 @@
+/**
+ * Hook for fetching user profile from Zitadel OIDC userinfo endpoint.
+ *
+ * GDPR Compliance: User profile data is never stored in our database.
+ * This hook fetches it directly from Zitadel when needed for display.
+ */
+
+import { useState, useEffect, useCallback } from "react";
+import * as SecureStore from "expo-secure-store";
+
+interface UserProfile {
+ sub: string;
+ name?: string;
+ preferredUsername?: string;
+ email?: string;
+ emailVerified?: boolean;
+ picture?: string;
+}
+
+const ACCESS_TOKEN_KEY = "zitadel_access_token";
+
+/**
+ * Hook to fetch and cache user profile from Zitadel userinfo endpoint.
+ * Profile is held in memory only - never persisted to our database.
+ */
+export function useUserProfile() {
+ const [profile, setProfile] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchProfile = useCallback(async () => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ // Get stored access token
+ const accessToken = await SecureStore.getItemAsync(ACCESS_TOKEN_KEY);
+
+ if (!accessToken) {
+ setProfile(null);
+ return null;
+ }
+
+ const issuer = process.env.EXPO_PUBLIC_ZITADEL_ISSUER;
+ if (!issuer) {
+ throw new Error("ZITADEL_ISSUER not configured");
+ }
+
+ // Fetch from OIDC userinfo endpoint
+ const response = await fetch(`${issuer}/oidc/v1/userinfo`, {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ // Token expired - clear it
+ await SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY);
+ setProfile(null);
+ return null;
+ }
+ throw new Error(`Failed to fetch user profile: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ const userProfile: UserProfile = {
+ sub: data.sub,
+ name: data.name,
+ preferredUsername: data.preferred_username,
+ email: data.email,
+ emailVerified: data.email_verified,
+ picture: data.picture,
+ };
+
+ setProfile(userProfile);
+ return userProfile;
+ } catch (err) {
+ const message = err instanceof Error ? err.message : "Failed to fetch profile";
+ setError(message);
+ console.error("[useUserProfile]", message);
+ return null;
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ const clearProfile = useCallback(() => {
+ setProfile(null);
+ setError(null);
+ }, []);
+
+ return {
+ profile,
+ isLoading,
+ error,
+ fetchProfile,
+ clearProfile,
+ };
+}
+
+/**
+ * Store access token after successful auth (called from useAuth).
+ */
+export async function storeAccessToken(token: string): Promise {
+ await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, token);
+}
+
+/**
+ * Clear access token on sign out.
+ */
+export async function clearAccessToken(): Promise {
+ await SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY);
+}
diff --git a/lib/recognition/cardDetector.ts b/lib/recognition/cardDetector.ts
new file mode 100644
index 0000000..c336df8
--- /dev/null
+++ b/lib/recognition/cardDetector.ts
@@ -0,0 +1,492 @@
+/**
+ * Card detection using edge detection and contour analysis.
+ * Detects card boundaries in images and returns the four corner points.
+ *
+ * This is a pure TypeScript implementation for when OpenCV is not available.
+ */
+
+import {
+ Point,
+ toGrayscale,
+ gaussianBlur,
+ distance,
+ crossProduct,
+ pointToLineDistance,
+} from "./imageUtils";
+
+/** Standard MTG card aspect ratio (height / width). Cards are 63mm x 88mm. */
+const CARD_ASPECT_RATIO = 88 / 63; // ~1.397
+const ASPECT_RATIO_TOLERANCE = 0.25;
+const MIN_CARD_AREA_RATIO = 0.05;
+const MAX_CARD_AREA_RATIO = 0.98;
+
+export interface CardDetectionResult {
+ found: boolean;
+ corners: Point[];
+ confidence: number;
+ debugMessage?: string;
+}
+
+/**
+ * Detect a card in the image and return its corner points.
+ */
+export function detectCard(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number
+): CardDetectionResult {
+ // Step 1: Convert to grayscale
+ const grayscale = toGrayscale(pixels);
+
+ // Step 2: Apply Gaussian blur to reduce noise
+ const blurred = gaussianBlur(grayscale, width, height, 5);
+
+ // Step 3: Apply Canny edge detection
+ const edges = applyCannyEdgeDetection(blurred, width, height, 50, 150);
+
+ // Step 4: Find contours
+ const contours = findContours(edges, width, height);
+
+ if (contours.length === 0) {
+ return { found: false, corners: [], confidence: 0, debugMessage: "No contours found" };
+ }
+
+ // Step 5: Find the best card-like quadrilateral
+ const imageArea = width * height;
+ const bestQuad = findBestCardQuadrilateral(contours, imageArea);
+
+ if (!bestQuad) {
+ return { found: false, corners: [], confidence: 0, debugMessage: "No card-like quadrilateral found" };
+ }
+
+ // Step 6: Order corners consistently
+ const orderedCorners = orderCorners(bestQuad);
+
+ // Calculate confidence
+ const confidence = calculateConfidence(orderedCorners, imageArea);
+
+ return { found: true, corners: orderedCorners, confidence };
+}
+
+/**
+ * Apply Canny edge detection.
+ */
+function applyCannyEdgeDetection(
+ gray: Uint8Array,
+ width: number,
+ height: number,
+ lowThreshold: number,
+ highThreshold: number
+): Uint8Array {
+ // Step 1: Compute gradients using Sobel operators
+ const gradientX = new Float32Array(width * height);
+ const gradientY = new Float32Array(width * height);
+ const magnitude = new Float32Array(width * height);
+ const direction = new Float32Array(width * height);
+
+ // Sobel kernels
+ const sobelX = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
+ const sobelY = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
+
+ for (let y = 1; y < height - 1; y++) {
+ for (let x = 1; x < width - 1; x++) {
+ let gx = 0, gy = 0;
+
+ for (let ky = -1; ky <= 1; ky++) {
+ for (let kx = -1; kx <= 1; kx++) {
+ const pixel = gray[(y + ky) * width + (x + kx)];
+ gx += pixel * sobelX[ky + 1][kx + 1];
+ gy += pixel * sobelY[ky + 1][kx + 1];
+ }
+ }
+
+ const idx = y * width + x;
+ gradientX[idx] = gx;
+ gradientY[idx] = gy;
+ magnitude[idx] = Math.sqrt(gx * gx + gy * gy);
+ direction[idx] = Math.atan2(gy, gx);
+ }
+ }
+
+ // Step 2: Non-maximum suppression
+ const suppressed = new Float32Array(width * height);
+
+ for (let y = 1; y < height - 1; y++) {
+ for (let x = 1; x < width - 1; x++) {
+ const idx = y * width + x;
+ let angle = direction[idx] * 180 / Math.PI;
+ if (angle < 0) angle += 180;
+
+ let neighbor1: number, neighbor2: number;
+
+ if (angle < 22.5 || angle >= 157.5) {
+ neighbor1 = magnitude[y * width + (x - 1)];
+ neighbor2 = magnitude[y * width + (x + 1)];
+ } else if (angle >= 22.5 && angle < 67.5) {
+ neighbor1 = magnitude[(y - 1) * width + (x + 1)];
+ neighbor2 = magnitude[(y + 1) * width + (x - 1)];
+ } else if (angle >= 67.5 && angle < 112.5) {
+ neighbor1 = magnitude[(y - 1) * width + x];
+ neighbor2 = magnitude[(y + 1) * width + x];
+ } else {
+ neighbor1 = magnitude[(y - 1) * width + (x - 1)];
+ neighbor2 = magnitude[(y + 1) * width + (x + 1)];
+ }
+
+ if (magnitude[idx] >= neighbor1 && magnitude[idx] >= neighbor2) {
+ suppressed[idx] = magnitude[idx];
+ }
+ }
+ }
+
+ // Step 3: Double thresholding and edge tracking
+ const result = new Uint8Array(width * height);
+ const strong = new Uint8Array(width * height);
+ const weak = new Uint8Array(width * height);
+
+ for (let i = 0; i < width * height; i++) {
+ if (suppressed[i] >= highThreshold) {
+ strong[i] = 1;
+ } else if (suppressed[i] >= lowThreshold) {
+ weak[i] = 1;
+ }
+ }
+
+ // Edge tracking by hysteresis
+ for (let y = 1; y < height - 1; y++) {
+ for (let x = 1; x < width - 1; x++) {
+ const idx = y * width + x;
+
+ if (strong[idx]) {
+ result[idx] = 255;
+ } else if (weak[idx]) {
+ // Check if connected to strong edge
+ outer: for (let dy = -1; dy <= 1; dy++) {
+ for (let dx = -1; dx <= 1; dx++) {
+ if (strong[(y + dy) * width + (x + dx)]) {
+ result[idx] = 255;
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Find contours in a binary edge image using flood fill.
+ */
+function findContours(
+ edges: Uint8Array,
+ width: number,
+ height: number
+): Point[][] {
+ const visited = new Uint8Array(width * height);
+ const contours: Point[][] = [];
+
+ const dx = [-1, 0, 1, 1, 1, 0, -1, -1];
+ const dy = [-1, -1, -1, 0, 1, 1, 1, 0];
+
+ for (let y = 1; y < height - 1; y++) {
+ for (let x = 1; x < width - 1; x++) {
+ const idx = y * width + x;
+ if (visited[idx]) continue;
+ if (edges[idx] < 128) continue;
+
+ // Trace contour using BFS
+ const contour: Point[] = [];
+ const queue: Array<{ x: number; y: number }> = [{ x, y }];
+
+ while (queue.length > 0 && contour.length < 10000) {
+ const point = queue.shift()!;
+ const pidx = point.y * width + point.x;
+
+ if (point.x < 0 || point.x >= width || point.y < 0 || point.y >= height) continue;
+ if (visited[pidx]) continue;
+ if (edges[pidx] < 128) continue;
+
+ visited[pidx] = 1;
+ contour.push(point);
+
+ for (let i = 0; i < 8; i++) {
+ queue.push({ x: point.x + dx[i], y: point.y + dy[i] });
+ }
+ }
+
+ if (contour.length >= 4) {
+ contours.push(contour);
+ }
+ }
+ }
+
+ return contours;
+}
+
+/**
+ * Find the best quadrilateral that matches a card shape.
+ */
+function findBestCardQuadrilateral(
+ contours: Point[][],
+ imageArea: number
+): Point[] | null {
+ let bestQuad: Point[] | null = null;
+ let bestScore = -Infinity;
+
+ for (const contour of contours) {
+ // Simplify contour using Douglas-Peucker
+ const simplified = simplifyContour(contour, contour.length * 0.02);
+
+ // Try to approximate as quadrilateral
+ const quad = approximateQuadrilateral(simplified);
+ if (!quad) continue;
+
+ // Check if valid card shape
+ const area = calculateQuadArea(quad);
+ const areaRatio = area / imageArea;
+
+ if (areaRatio < MIN_CARD_AREA_RATIO || areaRatio > MAX_CARD_AREA_RATIO) {
+ continue;
+ }
+
+ // Check aspect ratio
+ const aspectScore = calculateAspectRatioScore(quad);
+ if (aspectScore < 0.5) continue;
+
+ // Check convexity
+ if (!isConvex(quad)) continue;
+
+ // Score based on area and aspect ratio
+ const score = areaRatio * aspectScore;
+
+ if (score > bestScore) {
+ bestScore = score;
+ bestQuad = quad;
+ }
+ }
+
+ return bestQuad;
+}
+
+/**
+ * Simplify a contour using Douglas-Peucker algorithm.
+ */
+function simplifyContour(contour: Point[], epsilon: number): Point[] {
+ if (contour.length < 3) return contour;
+
+ const first = contour[0];
+ const last = contour[contour.length - 1];
+
+ let maxDist = 0;
+ let maxIndex = 0;
+
+ for (let i = 1; i < contour.length - 1; i++) {
+ const dist = pointToLineDistance(contour[i], first, last);
+ if (dist > maxDist) {
+ maxDist = dist;
+ maxIndex = i;
+ }
+ }
+
+ if (maxDist > epsilon) {
+ const left = simplifyContour(contour.slice(0, maxIndex + 1), epsilon);
+ const right = simplifyContour(contour.slice(maxIndex), epsilon);
+ return [...left.slice(0, -1), ...right];
+ }
+
+ return [first, last];
+}
+
+/**
+ * Try to approximate a contour as a quadrilateral.
+ */
+function approximateQuadrilateral(contour: Point[]): Point[] | null {
+ if (contour.length < 4) return null;
+ if (contour.length === 4) return contour;
+
+ // Find convex hull
+ const hull = convexHull(contour);
+ if (hull.length < 4) return null;
+ if (hull.length === 4) return hull;
+
+ // Find 4 extreme points
+ return findExtremePoints(hull);
+}
+
+/**
+ * Calculate convex hull using Graham scan.
+ */
+function convexHull(points: Point[]): Point[] {
+ if (points.length < 3) return points;
+
+ // Find bottom-most point
+ const start = points.reduce((min, p) =>
+ p.y < min.y || (p.y === min.y && p.x < min.x) ? p : min
+ );
+
+ // Sort by polar angle
+ const sorted = points
+ .filter(p => p !== start)
+ .sort((a, b) => {
+ const angleA = Math.atan2(a.y - start.y, a.x - start.x);
+ const angleB = Math.atan2(b.y - start.y, b.x - start.x);
+ if (angleA !== angleB) return angleA - angleB;
+ return distance(a, start) - distance(b, start);
+ });
+
+ const hull: Point[] = [start];
+
+ for (const point of sorted) {
+ while (hull.length > 1 && crossProduct(hull[hull.length - 2], hull[hull.length - 1], point) <= 0) {
+ hull.pop();
+ }
+ hull.push(point);
+ }
+
+ return hull;
+}
+
+/**
+ * Find 4 extreme points of a convex hull.
+ */
+function findExtremePoints(hull: Point[]): Point[] {
+ if (hull.length <= 4) return hull;
+
+ const minX = hull.reduce((min, p) => p.x < min.x ? p : min, hull[0]);
+ const maxX = hull.reduce((max, p) => p.x > max.x ? p : max, hull[0]);
+ const minY = hull.reduce((min, p) => p.y < min.y ? p : min, hull[0]);
+ const maxY = hull.reduce((max, p) => p.y > max.y ? p : max, hull[0]);
+
+ const extremes = new Set([minX, maxX, minY, maxY]);
+
+ if (extremes.size === 4) {
+ return Array.from(extremes);
+ }
+
+ // Fallback: take points at regular angular intervals
+ const centerX = hull.reduce((s, p) => s + p.x, 0) / hull.length;
+ const centerY = hull.reduce((s, p) => s + p.y, 0) / hull.length;
+
+ const sorted = [...hull].sort((a, b) =>
+ Math.atan2(a.y - centerY, a.x - centerX) -
+ Math.atan2(b.y - centerY, b.x - centerX)
+ );
+
+ const step = Math.floor(sorted.length / 4);
+ return [sorted[0], sorted[step], sorted[step * 2], sorted[step * 3]];
+}
+
+/**
+ * Calculate area of a quadrilateral using shoelace formula.
+ */
+function calculateQuadArea(quad: Point[]): number {
+ let area = 0;
+ for (let i = 0; i < 4; i++) {
+ const j = (i + 1) % 4;
+ area += quad[i].x * quad[j].y;
+ area -= quad[j].x * quad[i].y;
+ }
+ return Math.abs(area) / 2;
+}
+
+/**
+ * Calculate how well the aspect ratio matches a card.
+ */
+function calculateAspectRatioScore(quad: Point[]): number {
+ const width1 = distance(quad[0], quad[1]);
+ const width2 = distance(quad[2], quad[3]);
+ const height1 = distance(quad[1], quad[2]);
+ const height2 = distance(quad[3], quad[0]);
+
+ const avgWidth = (width1 + width2) / 2;
+ const avgHeight = (height1 + height2) / 2;
+
+ // Ensure we get the right ratio regardless of orientation
+ const aspectRatio = avgWidth > avgHeight
+ ? avgWidth / avgHeight
+ : avgHeight / avgWidth;
+
+ const deviation = Math.abs(aspectRatio - CARD_ASPECT_RATIO) / CARD_ASPECT_RATIO;
+
+ return Math.max(0, 1 - deviation / ASPECT_RATIO_TOLERANCE);
+}
+
+/**
+ * Check if a quadrilateral is convex.
+ */
+function isConvex(quad: Point[]): boolean {
+ let sign = 0;
+
+ for (let i = 0; i < 4; i++) {
+ const cross = crossProduct(
+ quad[i],
+ quad[(i + 1) % 4],
+ quad[(i + 2) % 4]
+ );
+
+ if (Math.abs(cross) < 0.0001) continue;
+
+ const currentSign = cross > 0 ? 1 : -1;
+
+ if (sign === 0) {
+ sign = currentSign;
+ } else if (sign !== currentSign) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Order corners consistently: top-left, top-right, bottom-right, bottom-left.
+ */
+function orderCorners(corners: Point[]): Point[] {
+ const centerX = corners.reduce((s, c) => s + c.x, 0) / 4;
+ const centerY = corners.reduce((s, c) => s + c.y, 0) / 4;
+
+ const topLeft = corners.filter(c => c.x < centerX && c.y < centerY)
+ .sort((a, b) => (a.x + a.y) - (b.x + b.y))[0];
+ const topRight = corners.filter(c => c.x >= centerX && c.y < centerY)
+ .sort((a, b) => (a.y - a.x) - (b.y - b.x))[0];
+ const bottomRight = corners.filter(c => c.x >= centerX && c.y >= centerY)
+ .sort((a, b) => (b.x + b.y) - (a.x + a.y))[0];
+ const bottomLeft = corners.filter(c => c.x < centerX && c.y >= centerY)
+ .sort((a, b) => (b.y - b.x) - (a.y - a.x))[0];
+
+ // Handle edge cases by sorting by angle
+ if (!topLeft || !topRight || !bottomRight || !bottomLeft) {
+ const sorted = [...corners].sort((a, b) =>
+ Math.atan2(a.y - centerY, a.x - centerX) -
+ Math.atan2(b.y - centerY, b.x - centerX)
+ );
+
+ // Find index of point with minimum sum (top-left)
+ let minSumIdx = 0;
+ let minSum = Infinity;
+ sorted.forEach((c, i) => {
+ const sum = c.x + c.y;
+ if (sum < minSum) {
+ minSum = sum;
+ minSumIdx = i;
+ }
+ });
+
+ return [...sorted.slice(minSumIdx), ...sorted.slice(0, minSumIdx)];
+ }
+
+ return [topLeft, topRight, bottomRight, bottomLeft];
+}
+
+/**
+ * Calculate confidence of the detection.
+ */
+function calculateConfidence(corners: Point[], imageArea: number): number {
+ const area = calculateQuadArea(corners);
+ const areaScore = Math.min(area / imageArea / 0.5, 1);
+ const aspectScore = calculateAspectRatioScore(corners);
+
+ return areaScore * 0.4 + aspectScore * 0.6;
+}
diff --git a/lib/recognition/clahe.ts b/lib/recognition/clahe.ts
new file mode 100644
index 0000000..14c5a43
--- /dev/null
+++ b/lib/recognition/clahe.ts
@@ -0,0 +1,278 @@
+/**
+ * CLAHE (Contrast Limited Adaptive Histogram Equalization) implementation.
+ *
+ * This is a pure TypeScript implementation for when OpenCV is not available.
+ * For better performance, use OpenCV's createCLAHE when available.
+ */
+
+interface CLAHEOptions {
+ clipLimit?: number;
+ tileGridSize?: { width: number; height: number };
+}
+
+const DEFAULT_OPTIONS: Required = {
+ clipLimit: 2.0,
+ tileGridSize: { width: 8, height: 8 },
+};
+
+/**
+ * Convert RGB to LAB color space.
+ * Returns L channel (0-100 range, scaled to 0-255 for processing).
+ */
+function rgbToLab(r: number, g: number, b: number): { l: number; a: number; b: number } {
+ // Normalize RGB to 0-1
+ let rNorm = r / 255;
+ let gNorm = g / 255;
+ let bNorm = b / 255;
+
+ // Apply gamma correction
+ rNorm = rNorm > 0.04045 ? Math.pow((rNorm + 0.055) / 1.055, 2.4) : rNorm / 12.92;
+ gNorm = gNorm > 0.04045 ? Math.pow((gNorm + 0.055) / 1.055, 2.4) : gNorm / 12.92;
+ bNorm = bNorm > 0.04045 ? Math.pow((bNorm + 0.055) / 1.055, 2.4) : bNorm / 12.92;
+
+ // Convert to XYZ
+ const x = (rNorm * 0.4124564 + gNorm * 0.3575761 + bNorm * 0.1804375) / 0.95047;
+ const y = rNorm * 0.2126729 + gNorm * 0.7151522 + bNorm * 0.0721750;
+ const z = (rNorm * 0.0193339 + gNorm * 0.1191920 + bNorm * 0.9503041) / 1.08883;
+
+ // Convert to LAB
+ const xNorm = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;
+ const yNorm = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;
+ const zNorm = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;
+
+ return {
+ l: Math.max(0, 116 * yNorm - 16), // 0-100
+ a: 500 * (xNorm - yNorm),
+ b: 200 * (yNorm - zNorm),
+ };
+}
+
+/**
+ * Convert LAB to RGB color space.
+ */
+function labToRgb(l: number, a: number, b: number): { r: number; g: number; b: number } {
+ // Convert LAB to XYZ
+ const yNorm = (l + 16) / 116;
+ const xNorm = a / 500 + yNorm;
+ const zNorm = yNorm - b / 200;
+
+ const x3 = Math.pow(xNorm, 3);
+ const y3 = Math.pow(yNorm, 3);
+ const z3 = Math.pow(zNorm, 3);
+
+ const x = (x3 > 0.008856 ? x3 : (xNorm - 16 / 116) / 7.787) * 0.95047;
+ const y = y3 > 0.008856 ? y3 : (yNorm - 16 / 116) / 7.787;
+ const z = (z3 > 0.008856 ? z3 : (zNorm - 16 / 116) / 7.787) * 1.08883;
+
+ // Convert XYZ to RGB
+ let rNorm = x * 3.2404542 + y * -1.5371385 + z * -0.4985314;
+ let gNorm = x * -0.9692660 + y * 1.8760108 + z * 0.0415560;
+ let bNorm = x * 0.0556434 + y * -0.2040259 + z * 1.0572252;
+
+ // Apply gamma correction
+ rNorm = rNorm > 0.0031308 ? 1.055 * Math.pow(rNorm, 1 / 2.4) - 0.055 : 12.92 * rNorm;
+ gNorm = gNorm > 0.0031308 ? 1.055 * Math.pow(gNorm, 1 / 2.4) - 0.055 : 12.92 * gNorm;
+ bNorm = bNorm > 0.0031308 ? 1.055 * Math.pow(bNorm, 1 / 2.4) - 0.055 : 12.92 * bNorm;
+
+ return {
+ r: Math.round(Math.max(0, Math.min(255, rNorm * 255))),
+ g: Math.round(Math.max(0, Math.min(255, gNorm * 255))),
+ b: Math.round(Math.max(0, Math.min(255, bNorm * 255))),
+ };
+}
+
+/**
+ * Compute histogram for a region.
+ */
+function computeHistogram(
+ data: Uint8Array,
+ startX: number,
+ startY: number,
+ width: number,
+ height: number,
+ stride: number
+): number[] {
+ const histogram = new Array(256).fill(0);
+
+ for (let y = startY; y < startY + height; y++) {
+ for (let x = startX; x < startX + width; x++) {
+ const value = data[y * stride + x];
+ histogram[value]++;
+ }
+ }
+
+ return histogram;
+}
+
+/**
+ * Clip histogram and redistribute excess.
+ */
+function clipHistogram(histogram: number[], clipLimit: number, numPixels: number): number[] {
+ const limit = Math.floor(clipLimit * numPixels / 256);
+ const clipped = [...histogram];
+
+ let excess = 0;
+ for (let i = 0; i < 256; i++) {
+ if (clipped[i] > limit) {
+ excess += clipped[i] - limit;
+ clipped[i] = limit;
+ }
+ }
+
+ // Redistribute excess
+ const increment = Math.floor(excess / 256);
+ const remainder = excess % 256;
+
+ for (let i = 0; i < 256; i++) {
+ clipped[i] += increment;
+ if (i < remainder) {
+ clipped[i]++;
+ }
+ }
+
+ return clipped;
+}
+
+/**
+ * Create lookup table from clipped histogram.
+ */
+function createLUT(histogram: number[], numPixels: number): Uint8Array {
+ const lut = new Uint8Array(256);
+ let cumSum = 0;
+
+ for (let i = 0; i < 256; i++) {
+ cumSum += histogram[i];
+ lut[i] = Math.round((cumSum / numPixels) * 255);
+ }
+
+ return lut;
+}
+
+/**
+ * Bilinear interpolation between four values.
+ */
+function bilinearInterpolate(
+ topLeft: number,
+ topRight: number,
+ bottomLeft: number,
+ bottomRight: number,
+ xFrac: number,
+ yFrac: number
+): number {
+ const top = topLeft + (topRight - topLeft) * xFrac;
+ const bottom = bottomLeft + (bottomRight - bottomLeft) * xFrac;
+ return Math.round(top + (bottom - top) * yFrac);
+}
+
+/**
+ * Apply CLAHE to RGBA pixel data.
+ * Processes only the L channel in LAB color space.
+ *
+ * @param pixels RGBA pixel data
+ * @param width Image width
+ * @param height Image height
+ * @param options CLAHE options
+ * @returns Processed RGBA pixel data
+ */
+export function applyCLAHE(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ options: CLAHEOptions = {}
+): Uint8Array {
+ const opts = { ...DEFAULT_OPTIONS, ...options };
+ const { clipLimit, tileGridSize } = opts;
+
+ const tileWidth = Math.floor(width / tileGridSize.width);
+ const tileHeight = Math.floor(height / tileGridSize.height);
+
+ // Extract L channel from LAB
+ const lChannel = new Uint8Array(width * height);
+ const aChannel = new Float32Array(width * height);
+ const bChannel = new Float32Array(width * height);
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const idx = (y * width + x) * 4;
+ const lab = rgbToLab(pixels[idx], pixels[idx + 1], pixels[idx + 2]);
+ const pixelIdx = y * width + x;
+ lChannel[pixelIdx] = Math.round((lab.l / 100) * 255);
+ aChannel[pixelIdx] = lab.a;
+ bChannel[pixelIdx] = lab.b;
+ }
+ }
+
+ // Compute LUTs for each tile
+ const luts: Uint8Array[][] = [];
+ const numPixelsPerTile = tileWidth * tileHeight;
+
+ for (let ty = 0; ty < tileGridSize.height; ty++) {
+ luts[ty] = [];
+ for (let tx = 0; tx < tileGridSize.width; tx++) {
+ const startX = tx * tileWidth;
+ const startY = ty * tileHeight;
+
+ const histogram = computeHistogram(
+ lChannel,
+ startX,
+ startY,
+ tileWidth,
+ tileHeight,
+ width
+ );
+
+ const clipped = clipHistogram(histogram, clipLimit, numPixelsPerTile);
+ luts[ty][tx] = createLUT(clipped, numPixelsPerTile);
+ }
+ }
+
+ // Apply CLAHE with bilinear interpolation
+ const result = new Uint8Array(pixels.length);
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const pixelIdx = y * width + x;
+ const rgbaIdx = pixelIdx * 4;
+
+ // Determine tile position
+ const tileX = Math.min(x / tileWidth, tileGridSize.width - 1);
+ const tileY = Math.min(y / tileHeight, tileGridSize.height - 1);
+
+ const tx = Math.floor(tileX);
+ const ty = Math.floor(tileY);
+
+ const xFrac = tileX - tx;
+ const yFrac = tileY - ty;
+
+ // Get surrounding tiles (handle edges)
+ const tx1 = Math.min(tx + 1, tileGridSize.width - 1);
+ const ty1 = Math.min(ty + 1, tileGridSize.height - 1);
+
+ const lValue = lChannel[pixelIdx];
+
+ // Interpolate LUT values
+ const newL = bilinearInterpolate(
+ luts[ty][tx][lValue],
+ luts[ty][tx1][lValue],
+ luts[ty1][tx][lValue],
+ luts[ty1][tx1][lValue],
+ xFrac,
+ yFrac
+ );
+
+ // Convert back to RGB
+ const rgb = labToRgb(
+ (newL / 255) * 100,
+ aChannel[pixelIdx],
+ bChannel[pixelIdx]
+ );
+
+ result[rgbaIdx] = rgb.r;
+ result[rgbaIdx + 1] = rgb.g;
+ result[rgbaIdx + 2] = rgb.b;
+ result[rgbaIdx + 3] = pixels[rgbaIdx + 3]; // Preserve alpha
+ }
+ }
+
+ return result;
+}
diff --git a/lib/recognition/imageLoader.ts b/lib/recognition/imageLoader.ts
new file mode 100644
index 0000000..0cc2813
--- /dev/null
+++ b/lib/recognition/imageLoader.ts
@@ -0,0 +1,95 @@
+/**
+ * Image loader utilities for React Native.
+ * Loads images from file paths and returns pixel data for recognition.
+ */
+
+import * as ImageManipulator from "expo-image-manipulator";
+import { Platform } from "react-native";
+
+export interface LoadedImage {
+ pixels: Uint8Array;
+ width: number;
+ height: number;
+}
+
+/**
+ * Load image from a file path and return RGBA pixel data.
+ * Uses expo-image-manipulator to resize the image to a manageable size first.
+ *
+ * Note: expo-image-manipulator doesn't directly provide pixel data.
+ * We need to use a canvas (web) or Skia (native) to decode pixels.
+ * For now, this provides the resized image URI for the Skia-based decoder.
+ */
+export async function loadImageForRecognition(
+ uri: string,
+ targetWidth: number = 480, // Reasonable size for processing
+ targetHeight: number = 640
+): Promise<{ uri: string; width: number; height: number }> {
+ // Normalize the URI for the platform
+ const normalizedUri =
+ Platform.OS === "android" && !uri.startsWith("file://")
+ ? `file://${uri}`
+ : uri;
+
+ // Resize the image for faster processing
+ const result = await ImageManipulator.manipulateAsync(
+ normalizedUri,
+ [
+ {
+ resize: {
+ width: targetWidth,
+ height: targetHeight,
+ },
+ },
+ ],
+ {
+ compress: 1,
+ format: ImageManipulator.SaveFormat.PNG,
+ }
+ );
+
+ return {
+ uri: result.uri,
+ width: result.width,
+ height: result.height,
+ };
+}
+
+/**
+ * Load image and get base64 data.
+ * Useful for passing to native modules or Skia.
+ */
+export async function loadImageAsBase64(
+ uri: string,
+ targetWidth: number = 480,
+ targetHeight: number = 640
+): Promise<{ base64: string; width: number; height: number }> {
+ // Normalize the URI
+ const normalizedUri =
+ Platform.OS === "android" && !uri.startsWith("file://")
+ ? `file://${uri}`
+ : uri;
+
+ const result = await ImageManipulator.manipulateAsync(
+ normalizedUri,
+ [
+ {
+ resize: {
+ width: targetWidth,
+ height: targetHeight,
+ },
+ },
+ ],
+ {
+ compress: 1,
+ format: ImageManipulator.SaveFormat.PNG,
+ base64: true,
+ }
+ );
+
+ return {
+ base64: result.base64 || "",
+ width: result.width,
+ height: result.height,
+ };
+}
diff --git a/lib/recognition/imageUtils.ts b/lib/recognition/imageUtils.ts
new file mode 100644
index 0000000..3f5b67b
--- /dev/null
+++ b/lib/recognition/imageUtils.ts
@@ -0,0 +1,292 @@
+/**
+ * Image utility functions for the recognition pipeline.
+ * Provides resize, rotation, and pixel manipulation helpers.
+ */
+
+export interface Point {
+ x: number;
+ y: number;
+}
+
+export interface Size {
+ width: number;
+ height: number;
+}
+
+/**
+ * Resize RGBA pixel data to target dimensions using bilinear interpolation.
+ */
+export function resizeImage(
+ pixels: Uint8Array | Uint8ClampedArray,
+ srcWidth: number,
+ srcHeight: number,
+ dstWidth: number,
+ dstHeight: number
+): Uint8Array {
+ const result = new Uint8Array(dstWidth * dstHeight * 4);
+
+ const xRatio = srcWidth / dstWidth;
+ const yRatio = srcHeight / dstHeight;
+
+ for (let y = 0; y < dstHeight; y++) {
+ for (let x = 0; x < dstWidth; x++) {
+ const srcX = x * xRatio;
+ const srcY = y * yRatio;
+
+ const x0 = Math.floor(srcX);
+ const y0 = Math.floor(srcY);
+ const x1 = Math.min(x0 + 1, srcWidth - 1);
+ const y1 = Math.min(y0 + 1, srcHeight - 1);
+
+ const xFrac = srcX - x0;
+ const yFrac = srcY - y0;
+
+ const dstIdx = (y * dstWidth + x) * 4;
+
+ for (let c = 0; c < 4; c++) {
+ const idx00 = (y0 * srcWidth + x0) * 4 + c;
+ const idx10 = (y0 * srcWidth + x1) * 4 + c;
+ const idx01 = (y1 * srcWidth + x0) * 4 + c;
+ const idx11 = (y1 * srcWidth + x1) * 4 + c;
+
+ const top = pixels[idx00] + (pixels[idx10] - pixels[idx00]) * xFrac;
+ const bottom = pixels[idx01] + (pixels[idx11] - pixels[idx01]) * xFrac;
+ result[dstIdx + c] = Math.round(top + (bottom - top) * yFrac);
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Rotate RGBA pixel data by 90, 180, or 270 degrees.
+ */
+export function rotateImage(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ degrees: 0 | 90 | 180 | 270
+): { pixels: Uint8Array; width: number; height: number } {
+ if (degrees === 0) {
+ return { pixels: new Uint8Array(pixels), width, height };
+ }
+
+ const [newWidth, newHeight] = degrees === 90 || degrees === 270
+ ? [height, width]
+ : [width, height];
+
+ const result = new Uint8Array(newWidth * newHeight * 4);
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const srcIdx = (y * width + x) * 4;
+
+ let dstX: number, dstY: number;
+
+ switch (degrees) {
+ case 90:
+ dstX = height - 1 - y;
+ dstY = x;
+ break;
+ case 180:
+ dstX = width - 1 - x;
+ dstY = height - 1 - y;
+ break;
+ case 270:
+ dstX = y;
+ dstY = width - 1 - x;
+ break;
+ default:
+ dstX = x;
+ dstY = y;
+ }
+
+ const dstIdx = (dstY * newWidth + dstX) * 4;
+ result[dstIdx] = pixels[srcIdx];
+ result[dstIdx + 1] = pixels[srcIdx + 1];
+ result[dstIdx + 2] = pixels[srcIdx + 2];
+ result[dstIdx + 3] = pixels[srcIdx + 3];
+ }
+ }
+
+ return { pixels: result, width: newWidth, height: newHeight };
+}
+
+/**
+ * Convert RGBA to grayscale using luminance formula.
+ */
+export function toGrayscale(pixels: Uint8Array | Uint8ClampedArray): Uint8Array {
+ const numPixels = pixels.length / 4;
+ const gray = new Uint8Array(numPixels);
+
+ for (let i = 0; i < numPixels; i++) {
+ const idx = i * 4;
+ gray[i] = Math.round(
+ 0.299 * pixels[idx] +
+ 0.587 * pixels[idx + 1] +
+ 0.114 * pixels[idx + 2]
+ );
+ }
+
+ return gray;
+}
+
+/**
+ * Convert grayscale back to RGBA.
+ */
+export function grayscaleToRgba(
+ gray: Uint8Array,
+ width: number,
+ height: number
+): Uint8Array {
+ const rgba = new Uint8Array(width * height * 4);
+
+ for (let i = 0; i < gray.length; i++) {
+ const idx = i * 4;
+ rgba[idx] = gray[i];
+ rgba[idx + 1] = gray[i];
+ rgba[idx + 2] = gray[i];
+ rgba[idx + 3] = 255;
+ }
+
+ return rgba;
+}
+
+/**
+ * Apply Gaussian blur to grayscale image.
+ * Uses separable 1D convolution for efficiency.
+ */
+export function gaussianBlur(
+ gray: Uint8Array,
+ width: number,
+ height: number,
+ radius: number = 5
+): Uint8Array {
+ // Generate Gaussian kernel
+ const sigma = radius / 2;
+ const size = radius * 2 + 1;
+ const kernel = new Float32Array(size);
+ let sum = 0;
+
+ for (let i = 0; i < size; i++) {
+ const x = i - radius;
+ kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma));
+ sum += kernel[i];
+ }
+
+ // Normalize
+ for (let i = 0; i < size; i++) {
+ kernel[i] /= sum;
+ }
+
+ // Horizontal pass
+ const temp = new Uint8Array(width * height);
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ let value = 0;
+ for (let k = -radius; k <= radius; k++) {
+ const sx = Math.max(0, Math.min(width - 1, x + k));
+ value += gray[y * width + sx] * kernel[k + radius];
+ }
+ temp[y * width + x] = Math.round(value);
+ }
+ }
+
+ // Vertical pass
+ const result = new Uint8Array(width * height);
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ let value = 0;
+ for (let k = -radius; k <= radius; k++) {
+ const sy = Math.max(0, Math.min(height - 1, y + k));
+ value += temp[sy * width + x] * kernel[k + radius];
+ }
+ result[y * width + x] = Math.round(value);
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Calculate distance between two points.
+ */
+export function distance(a: Point, b: Point): number {
+ const dx = b.x - a.x;
+ const dy = b.y - a.y;
+ return Math.sqrt(dx * dx + dy * dy);
+}
+
+/**
+ * Calculate cross product of vectors (b-a) and (c-b).
+ */
+export function crossProduct(a: Point, b: Point, c: Point): number {
+ return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
+}
+
+/**
+ * Calculate point-to-line distance.
+ */
+export function pointToLineDistance(point: Point, lineStart: Point, lineEnd: Point): number {
+ const dx = lineEnd.x - lineStart.x;
+ const dy = lineEnd.y - lineStart.y;
+ const lengthSquared = dx * dx + dy * dy;
+
+ if (lengthSquared === 0) {
+ return distance(point, lineStart);
+ }
+
+ let t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / lengthSquared;
+ t = Math.max(0, Math.min(1, t));
+
+ const projection = {
+ x: lineStart.x + t * dx,
+ y: lineStart.y + t * dy,
+ };
+
+ return distance(point, projection);
+}
+
+/**
+ * Sample a pixel from RGBA data using bilinear interpolation.
+ */
+export function sampleBilinear(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ x: number,
+ y: number
+): [number, number, number, number] {
+ // Clamp to valid range
+ x = Math.max(0, Math.min(width - 1, x));
+ y = Math.max(0, Math.min(height - 1, y));
+
+ const x0 = Math.floor(x);
+ const y0 = Math.floor(y);
+ const x1 = Math.min(x0 + 1, width - 1);
+ const y1 = Math.min(y0 + 1, height - 1);
+
+ const xFrac = x - x0;
+ const yFrac = y - y0;
+
+ const idx00 = (y0 * width + x0) * 4;
+ const idx10 = (y0 * width + x1) * 4;
+ const idx01 = (y1 * width + x0) * 4;
+ const idx11 = (y1 * width + x1) * 4;
+
+ const result: [number, number, number, number] = [0, 0, 0, 0];
+
+ for (let c = 0; c < 4; c++) {
+ const c00 = pixels[idx00 + c];
+ const c10 = pixels[idx10 + c];
+ const c01 = pixels[idx01 + c];
+ const c11 = pixels[idx11 + c];
+
+ const top = c00 + (c10 - c00) * xFrac;
+ const bottom = c01 + (c11 - c01) * xFrac;
+ result[c] = Math.round(top + (bottom - top) * yFrac);
+ }
+
+ return result;
+}
diff --git a/lib/recognition/index.ts b/lib/recognition/index.ts
new file mode 100644
index 0000000..087a0fd
--- /dev/null
+++ b/lib/recognition/index.ts
@@ -0,0 +1,67 @@
+/**
+ * Card recognition module exports.
+ */
+
+// Recognition service
+export {
+ recognizeCard,
+ computeImageHash,
+ calculateConfidence,
+ createRecognitionService,
+ type RecognitionOptions,
+ type RecognitionResult,
+ type CardMatch,
+ type CardHashEntry,
+} from "./recognitionService";
+
+// Card detection
+export {
+ detectCard,
+ type CardDetectionResult,
+} from "./cardDetector";
+
+// Perspective correction
+export {
+ warpPerspective,
+ OUTPUT_WIDTH,
+ OUTPUT_HEIGHT,
+} from "./perspectiveCorrection";
+
+// CLAHE preprocessing
+export { applyCLAHE } from "./clahe";
+
+// Perceptual hashing
+export {
+ computeColorHash,
+ hammingDistance,
+ hashToHex,
+ hexToHash,
+ HASH_VERSION,
+ MATCH_THRESHOLD,
+ HASH_BITS,
+} from "./perceptualHash";
+
+// Image utilities
+export {
+ resizeImage,
+ rotateImage,
+ toGrayscale,
+ grayscaleToRgba,
+ gaussianBlur,
+ distance,
+ type Point,
+ type Size,
+} from "./imageUtils";
+
+// Image loading
+export {
+ loadImageForRecognition,
+ loadImageAsBase64,
+} from "./imageLoader";
+
+// Skia image decoding
+export {
+ decodeImageBase64,
+ decodeImageFromUri,
+ useDecodedImage,
+} from "./skiaDecoder";
diff --git a/lib/recognition/perceptualHash.ts b/lib/recognition/perceptualHash.ts
new file mode 100644
index 0000000..a6bb0ff
--- /dev/null
+++ b/lib/recognition/perceptualHash.ts
@@ -0,0 +1,211 @@
+/**
+ * Perceptual hashing implementation using DCT (Discrete Cosine Transform).
+ * Computes a 192-bit (24 byte) color hash from an image.
+ *
+ * The hash is computed by:
+ * 1. Resizing to 32x32
+ * 2. For each RGB channel:
+ * - Apply 2D DCT
+ * - Extract 8x8 low-frequency coefficients (skip DC)
+ * - Compare each to median -> 63 bits per channel
+ * 3. Concatenate R, G, B hashes -> 24 bytes (192 bits)
+ */
+
+const DCT_SIZE = 32;
+const HASH_SIZE = 8;
+const BITS_PER_CHANNEL = 63; // 8x8 - 1 (skip DC)
+
+/**
+ * Precomputed cosine values for DCT.
+ */
+const cosineCache: number[][] = [];
+
+function initCosineCache(): void {
+ if (cosineCache.length > 0) return;
+
+ for (let i = 0; i < DCT_SIZE; i++) {
+ cosineCache[i] = [];
+ for (let j = 0; j < DCT_SIZE; j++) {
+ cosineCache[i][j] = Math.cos((Math.PI / DCT_SIZE) * (j + 0.5) * i);
+ }
+ }
+}
+
+/**
+ * Apply 2D DCT to a matrix.
+ */
+function applyDCT2D(matrix: number[][]): number[][] {
+ initCosineCache();
+
+ const result: number[][] = [];
+
+ for (let u = 0; u < DCT_SIZE; u++) {
+ result[u] = [];
+ for (let v = 0; v < DCT_SIZE; v++) {
+ let sum = 0;
+ for (let i = 0; i < DCT_SIZE; i++) {
+ for (let j = 0; j < DCT_SIZE; j++) {
+ sum += matrix[i][j] * cosineCache[u][i] * cosineCache[v][j];
+ }
+ }
+ const cu = u === 0 ? 1 / Math.sqrt(2) : 1;
+ const cv = v === 0 ? 1 / Math.sqrt(2) : 1;
+ result[u][v] = (cu * cv * sum) / 4;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Get the median of an array of numbers.
+ */
+function getMedian(values: number[]): number {
+ const sorted = [...values].sort((a, b) => a - b);
+ const mid = Math.floor(sorted.length / 2);
+ return sorted.length % 2 !== 0
+ ? sorted[mid]
+ : (sorted[mid - 1] + sorted[mid]) / 2;
+}
+
+/**
+ * Convert a BigInt to a Uint8Array of specified length.
+ */
+function bigintToBytes(value: bigint, length: number): Uint8Array {
+ const bytes = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ bytes[i] = Number((value >> BigInt(i * 8)) & 0xFFn);
+ }
+ return bytes;
+}
+
+/**
+ * Compute hash for a single color channel.
+ */
+function computeChannelHash(channel: number[][]): Uint8Array {
+ const dct = applyDCT2D(channel);
+
+ // Extract 8x8 low-frequency coefficients, skip DC (0,0)
+ const lowFreq: number[] = [];
+ for (let i = 0; i < HASH_SIZE; i++) {
+ for (let j = 0; j < HASH_SIZE; j++) {
+ if (i === 0 && j === 0) continue; // Skip DC component
+ lowFreq.push(dct[i][j]);
+ }
+ }
+
+ const median = getMedian(lowFreq);
+
+ // Generate 63-bit hash
+ let bits = 0n;
+ for (let i = 0; i < lowFreq.length; i++) {
+ if (lowFreq[i] > median) {
+ bits |= 1n << BigInt(i);
+ }
+ }
+
+ return bigintToBytes(bits, 8);
+}
+
+/**
+ * Extract a color channel from RGBA pixel data.
+ * @param pixels RGBA pixel data (width * height * 4)
+ * @param width Image width
+ * @param height Image height
+ * @param channel 0=R, 1=G, 2=B
+ */
+function extractChannel(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ channel: 0 | 1 | 2
+): number[][] {
+ const matrix: number[][] = [];
+
+ for (let y = 0; y < height; y++) {
+ matrix[y] = [];
+ for (let x = 0; x < width; x++) {
+ const idx = (y * width + x) * 4;
+ matrix[y][x] = pixels[idx + channel];
+ }
+ }
+
+ return matrix;
+}
+
+/**
+ * Compute a 192-bit perceptual color hash from RGBA pixel data.
+ * The image should already be resized to 32x32.
+ *
+ * @param pixels RGBA pixel data (32 * 32 * 4 = 4096 bytes)
+ * @returns 24-byte hash (8 bytes per RGB channel)
+ */
+export function computeColorHash(pixels: Uint8Array | Uint8ClampedArray): Uint8Array {
+ if (pixels.length !== DCT_SIZE * DCT_SIZE * 4) {
+ throw new Error(`Expected ${DCT_SIZE * DCT_SIZE * 4} bytes, got ${pixels.length}`);
+ }
+
+ const rChannel = extractChannel(pixels, DCT_SIZE, DCT_SIZE, 0);
+ const gChannel = extractChannel(pixels, DCT_SIZE, DCT_SIZE, 1);
+ const bChannel = extractChannel(pixels, DCT_SIZE, DCT_SIZE, 2);
+
+ const rHash = computeChannelHash(rChannel);
+ const gHash = computeChannelHash(gChannel);
+ const bHash = computeChannelHash(bChannel);
+
+ // Combine all channels
+ const combined = new Uint8Array(24);
+ combined.set(rHash, 0);
+ combined.set(gHash, 8);
+ combined.set(bHash, 16);
+
+ return combined;
+}
+
+/**
+ * Compute Hamming distance between two hashes.
+ * Lower distance = more similar.
+ */
+export function hammingDistance(a: Uint8Array, b: Uint8Array): number {
+ if (a.length !== b.length) {
+ throw new Error(`Hash length mismatch: ${a.length} vs ${b.length}`);
+ }
+
+ let distance = 0;
+ for (let i = 0; i < a.length; i++) {
+ let xor = a[i] ^ b[i];
+ while (xor) {
+ distance += xor & 1;
+ xor >>>= 1;
+ }
+ }
+
+ return distance;
+}
+
+/**
+ * Convert hash to hex string for display/storage.
+ */
+export function hashToHex(hash: Uint8Array): string {
+ return Array.from(hash)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join("");
+}
+
+/**
+ * Convert hex string back to hash.
+ */
+export function hexToHash(hex: string): Uint8Array {
+ const bytes = new Uint8Array(hex.length / 2);
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
+ }
+ return bytes;
+}
+
+// Hash algorithm version for migrations
+export const HASH_VERSION = 1;
+
+// Matching thresholds
+export const MATCH_THRESHOLD = 25; // Max Hamming distance for a match
+export const HASH_BITS = 192; // Total bits in hash
diff --git a/lib/recognition/perspectiveCorrection.ts b/lib/recognition/perspectiveCorrection.ts
new file mode 100644
index 0000000..27afeaf
--- /dev/null
+++ b/lib/recognition/perspectiveCorrection.ts
@@ -0,0 +1,207 @@
+/**
+ * Perspective correction to transform a quadrilateral region into a rectangle.
+ * Uses homography transformation with bilinear sampling.
+ */
+
+import { Point, sampleBilinear } from "./imageUtils";
+
+/** Standard output size for corrected card images (63:88 aspect ratio). */
+export const OUTPUT_WIDTH = 480;
+export const OUTPUT_HEIGHT = 670;
+
+/**
+ * Apply perspective correction to extract and normalize a card from an image.
+ *
+ * @param pixels - Source RGBA pixel data
+ * @param width - Source image width
+ * @param height - Source image height
+ * @param corners - Four corners in order: top-left, top-right, bottom-right, bottom-left
+ * @param outputWidth - Width of output image
+ * @param outputHeight - Height of output image
+ * @returns Perspective-corrected RGBA pixel data
+ */
+export function warpPerspective(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ corners: Point[],
+ outputWidth: number = OUTPUT_WIDTH,
+ outputHeight: number = OUTPUT_HEIGHT
+): { pixels: Uint8Array; width: number; height: number } {
+ if (corners.length !== 4) {
+ throw new Error("Exactly 4 corners required");
+ }
+
+ // Determine if card is landscape (rotated 90°)
+ const width1 = distance(corners[0], corners[1]);
+ const height1 = distance(corners[1], corners[2]);
+
+ let orderedCorners: Point[];
+
+ if (width1 > height1) {
+ // Card is landscape - rotate corners to portrait
+ orderedCorners = [corners[1], corners[2], corners[3], corners[0]];
+ } else {
+ orderedCorners = corners;
+ }
+
+ // Compute the perspective transform matrix
+ const matrix = computePerspectiveTransform(orderedCorners, outputWidth, outputHeight);
+ const inverse = invertMatrix3x3(matrix);
+
+ // Apply the transform
+ const result = new Uint8Array(outputWidth * outputHeight * 4);
+
+ for (let y = 0; y < outputHeight; y++) {
+ for (let x = 0; x < outputWidth; x++) {
+ // Apply inverse transform to find source coordinates
+ const srcPoint = applyTransform(inverse, x, y);
+
+ // Bilinear interpolation for smooth sampling
+ const [r, g, b, a] = sampleBilinear(pixels, width, height, srcPoint.x, srcPoint.y);
+
+ const idx = (y * outputWidth + x) * 4;
+ result[idx] = r;
+ result[idx + 1] = g;
+ result[idx + 2] = b;
+ result[idx + 3] = a;
+ }
+ }
+
+ return { pixels: result, width: outputWidth, height: outputHeight };
+}
+
+/**
+ * Compute a perspective transform matrix from quad corners to rectangle.
+ * Uses the Direct Linear Transform (DLT) algorithm.
+ */
+function computePerspectiveTransform(
+ src: Point[],
+ dstWidth: number,
+ dstHeight: number
+): number[] {
+ // Destination corners (rectangle)
+ const dst: Point[] = [
+ { x: 0, y: 0 },
+ { x: dstWidth - 1, y: 0 },
+ { x: dstWidth - 1, y: dstHeight - 1 },
+ { x: 0, y: dstHeight - 1 },
+ ];
+
+ // Build the 8x8 matrix for solving the homography
+ const A: number[][] = [];
+ const b: number[] = [];
+
+ for (let i = 0; i < 4; i++) {
+ const sx = src[i].x;
+ const sy = src[i].y;
+ const dx = dst[i].x;
+ const dy = dst[i].y;
+
+ A.push([sx, sy, 1, 0, 0, 0, -dx * sx, -dx * sy]);
+ b.push(dx);
+
+ A.push([0, 0, 0, sx, sy, 1, -dy * sx, -dy * sy]);
+ b.push(dy);
+ }
+
+ // Solve using Gaussian elimination
+ const h = solveLinearSystem(A, b);
+
+ // Return 3x3 matrix as flat array [h11, h12, h13, h21, h22, h23, h31, h32, h33]
+ return [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1];
+}
+
+/**
+ * Solve a linear system Ax = b using Gaussian elimination with partial pivoting.
+ */
+function solveLinearSystem(A: number[][], b: number[]): number[] {
+ const n = b.length;
+
+ // Create augmented matrix
+ const augmented: number[][] = A.map((row, i) => [...row, b[i]]);
+
+ // Forward elimination with partial pivoting
+ for (let col = 0; col < n; col++) {
+ // Find pivot
+ let maxRow = col;
+ for (let row = col + 1; row < n; row++) {
+ if (Math.abs(augmented[row][col]) > Math.abs(augmented[maxRow][col])) {
+ maxRow = row;
+ }
+ }
+
+ // Swap rows
+ [augmented[col], augmented[maxRow]] = [augmented[maxRow], augmented[col]];
+
+ // Eliminate
+ for (let row = col + 1; row < n; row++) {
+ if (Math.abs(augmented[col][col]) < 1e-10) continue;
+
+ const factor = augmented[row][col] / augmented[col][col];
+ for (let j = col; j <= n; j++) {
+ augmented[row][j] -= factor * augmented[col][j];
+ }
+ }
+ }
+
+ // Back substitution
+ const x = new Array(n).fill(0);
+ for (let i = n - 1; i >= 0; i--) {
+ x[i] = augmented[i][n];
+ for (let j = i + 1; j < n; j++) {
+ x[i] -= augmented[i][j] * x[j];
+ }
+ if (Math.abs(augmented[i][i]) > 1e-10) {
+ x[i] /= augmented[i][i];
+ }
+ }
+
+ return x;
+}
+
+/**
+ * Apply a 3x3 transform matrix to a point.
+ */
+function applyTransform(H: number[], x: number, y: number): Point {
+ let w = H[6] * x + H[7] * y + H[8];
+ if (Math.abs(w) < 1e-10) w = 1e-10;
+
+ return {
+ x: (H[0] * x + H[1] * y + H[2]) / w,
+ y: (H[3] * x + H[4] * y + H[5]) / w,
+ };
+}
+
+/**
+ * Invert a 3x3 matrix.
+ */
+function invertMatrix3x3(m: number[]): number[] {
+ const det =
+ m[0] * (m[4] * m[8] - m[5] * m[7]) -
+ m[1] * (m[3] * m[8] - m[5] * m[6]) +
+ m[2] * (m[3] * m[7] - m[4] * m[6]);
+
+ const invDet = Math.abs(det) < 1e-10 ? 1e10 : 1 / det;
+
+ return [
+ (m[4] * m[8] - m[5] * m[7]) * invDet,
+ (m[2] * m[7] - m[1] * m[8]) * invDet,
+ (m[1] * m[5] - m[2] * m[4]) * invDet,
+ (m[5] * m[6] - m[3] * m[8]) * invDet,
+ (m[0] * m[8] - m[2] * m[6]) * invDet,
+ (m[2] * m[3] - m[0] * m[5]) * invDet,
+ (m[3] * m[7] - m[4] * m[6]) * invDet,
+ (m[1] * m[6] - m[0] * m[7]) * invDet,
+ (m[0] * m[4] - m[1] * m[3]) * invDet,
+ ];
+}
+
+/**
+ * Calculate distance between two points.
+ */
+function distance(a: Point, b: Point): number {
+ const dx = b.x - a.x;
+ const dy = b.y - a.y;
+ return Math.sqrt(dx * dx + dy * dy);
+}
diff --git a/lib/recognition/recognitionService.ts b/lib/recognition/recognitionService.ts
new file mode 100644
index 0000000..8492d9c
--- /dev/null
+++ b/lib/recognition/recognitionService.ts
@@ -0,0 +1,313 @@
+/**
+ * Card recognition service that orchestrates the full pipeline.
+ *
+ * Pipeline:
+ * 1. Card detection (optional) - find card boundaries
+ * 2. Perspective correction - warp to rectangle
+ * 3. CLAHE preprocessing - normalize lighting
+ * 4. Resize to 32x32
+ * 5. Compute perceptual hash
+ * 6. Match against database via Hamming distance
+ */
+
+import { detectCard, CardDetectionResult } from "./cardDetector";
+import { warpPerspective } from "./perspectiveCorrection";
+import { applyCLAHE } from "./clahe";
+import {
+ computeColorHash,
+ hammingDistance,
+ MATCH_THRESHOLD,
+ HASH_BITS,
+} from "./perceptualHash";
+import { resizeImage, rotateImage } from "./imageUtils";
+
+export interface RecognitionOptions {
+ /** Enable card detection and perspective correction. */
+ enableCardDetection?: boolean;
+ /** Enable rotation matching (try 0°, 90°, 180°, 270°). */
+ enableRotationMatching?: boolean;
+ /** Minimum confidence to accept a match (0-1). */
+ minConfidence?: number;
+ /** Maximum Hamming distance to accept a match. */
+ matchThreshold?: number;
+}
+
+export interface CardMatch {
+ /** Card ID from database. */
+ cardId: string;
+ /** Match confidence (0-1). */
+ confidence: number;
+ /** Hamming distance between hashes. */
+ distance: number;
+ /** Rotation used for match (0, 90, 180, or 270). */
+ rotation: number;
+}
+
+export interface RecognitionResult {
+ success: boolean;
+ match?: CardMatch;
+ cardDetection?: CardDetectionResult;
+ error?: string;
+ processingTimeMs: number;
+}
+
+export interface CardHashEntry {
+ id: string;
+ name: string;
+ setCode: string;
+ collectorNumber?: string;
+ imageUri?: string;
+ hash: Uint8Array;
+}
+
+const DEFAULT_OPTIONS: Required = {
+ enableCardDetection: true,
+ enableRotationMatching: true,
+ minConfidence: 0.85,
+ matchThreshold: MATCH_THRESHOLD,
+};
+
+/**
+ * Calculate confidence from Hamming distance.
+ */
+export function calculateConfidence(distance: number, totalBits: number = HASH_BITS): number {
+ return 1 - distance / totalBits;
+}
+
+/**
+ * Recognize a card from RGBA pixel data.
+ *
+ * @param pixels - RGBA pixel data
+ * @param width - Image width
+ * @param height - Image height
+ * @param cardHashes - Array of card hashes to match against
+ * @param options - Recognition options
+ * @returns Recognition result with best match
+ */
+export function recognizeCard(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ cardHashes: CardHashEntry[],
+ options: RecognitionOptions = {}
+): RecognitionResult {
+ const startTime = performance.now();
+ const opts = { ...DEFAULT_OPTIONS, ...options };
+
+ try {
+ if (cardHashes.length === 0) {
+ return {
+ success: false,
+ error: "No cards in database",
+ processingTimeMs: performance.now() - startTime,
+ };
+ }
+
+ let cardPixels = pixels;
+ let cardWidth = width;
+ let cardHeight = height;
+ let detection: CardDetectionResult | undefined;
+
+ // Step 1: Detect and extract card (if enabled)
+ if (opts.enableCardDetection) {
+ detection = detectCard(pixels, width, height);
+
+ if (detection.found) {
+ const warped = warpPerspective(
+ pixels,
+ width,
+ height,
+ detection.corners
+ );
+ cardPixels = warped.pixels;
+ cardWidth = warped.width;
+ cardHeight = warped.height;
+ }
+ }
+
+ // Step 2: Find best match (with or without rotation)
+ const match = opts.enableRotationMatching
+ ? findBestMatchWithRotations(cardPixels, cardWidth, cardHeight, cardHashes, opts)
+ : findBestMatchSingle(cardPixels, cardWidth, cardHeight, cardHashes, opts);
+
+ const processingTimeMs = performance.now() - startTime;
+
+ if (!match) {
+ return {
+ success: false,
+ cardDetection: detection,
+ error: "No match found",
+ processingTimeMs,
+ };
+ }
+
+ return {
+ success: true,
+ match,
+ cardDetection: detection,
+ processingTimeMs,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "Unknown error",
+ processingTimeMs: performance.now() - startTime,
+ };
+ }
+}
+
+/**
+ * Compute hash for an image with full preprocessing pipeline.
+ */
+export function computeImageHash(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number
+): Uint8Array {
+ // Apply CLAHE
+ const clahePixels = applyCLAHE(pixels, width, height);
+
+ // Resize to 32x32
+ const resized = resizeImage(clahePixels, width, height, 32, 32);
+
+ // Compute hash
+ return computeColorHash(resized);
+}
+
+/**
+ * Find best match trying all 4 rotations.
+ */
+function findBestMatchWithRotations(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ cardHashes: CardHashEntry[],
+ opts: Required
+): CardMatch | null {
+ let bestMatch: CardMatch | null = null;
+
+ const rotations: Array<0 | 90 | 180 | 270> = [0, 90, 180, 270];
+
+ for (const rotation of rotations) {
+ const { pixels: rotatedPixels, width: rotatedWidth, height: rotatedHeight } =
+ rotation === 0
+ ? { pixels, width, height }
+ : rotateImage(pixels, width, height, rotation);
+
+ // Apply CLAHE
+ const clahePixels = applyCLAHE(rotatedPixels, rotatedWidth, rotatedHeight);
+
+ // Resize to 32x32
+ const resized = resizeImage(clahePixels, rotatedWidth, rotatedHeight, 32, 32);
+
+ // Compute hash
+ const queryHash = computeColorHash(resized);
+
+ // Find best match for this rotation
+ for (const card of cardHashes) {
+ if (!card.hash || card.hash.length !== queryHash.length) continue;
+
+ const distance = hammingDistance(queryHash, card.hash);
+ const confidence = calculateConfidence(distance);
+
+ if (distance <= opts.matchThreshold && confidence >= opts.minConfidence) {
+ if (!bestMatch || distance < bestMatch.distance) {
+ bestMatch = {
+ cardId: card.id,
+ confidence,
+ distance,
+ rotation,
+ };
+ }
+
+ // Early exit on perfect match
+ if (distance === 0) {
+ return bestMatch;
+ }
+ }
+ }
+ }
+
+ return bestMatch;
+}
+
+/**
+ * Find best match without rotation.
+ */
+function findBestMatchSingle(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ cardHashes: CardHashEntry[],
+ opts: Required
+): CardMatch | null {
+ // Apply CLAHE
+ const clahePixels = applyCLAHE(pixels, width, height);
+
+ // Resize to 32x32
+ const resized = resizeImage(clahePixels, width, height, 32, 32);
+
+ // Compute hash
+ const queryHash = computeColorHash(resized);
+
+ let bestMatch: CardMatch | null = null;
+
+ for (const card of cardHashes) {
+ if (!card.hash || card.hash.length !== queryHash.length) continue;
+
+ const distance = hammingDistance(queryHash, card.hash);
+ const confidence = calculateConfidence(distance);
+
+ if (distance <= opts.matchThreshold && confidence >= opts.minConfidence) {
+ if (!bestMatch || distance < bestMatch.distance) {
+ bestMatch = {
+ cardId: card.id,
+ confidence,
+ distance,
+ rotation: 0,
+ };
+ }
+
+ if (distance === 0) {
+ return bestMatch;
+ }
+ }
+ }
+
+ return bestMatch;
+}
+
+/**
+ * Create a recognition service instance with cached card hashes.
+ */
+export function createRecognitionService(cardHashes: CardHashEntry[]) {
+ let cachedHashes = cardHashes;
+
+ return {
+ /**
+ * Recognize a card from RGBA pixel data.
+ */
+ recognize(
+ pixels: Uint8Array | Uint8ClampedArray,
+ width: number,
+ height: number,
+ options?: RecognitionOptions
+ ): RecognitionResult {
+ return recognizeCard(pixels, width, height, cachedHashes, options);
+ },
+
+ /**
+ * Update the cached card hashes.
+ */
+ updateHashes(hashes: CardHashEntry[]) {
+ cachedHashes = hashes;
+ },
+
+ /**
+ * Get the number of cached hashes.
+ */
+ getHashCount(): number {
+ return cachedHashes.length;
+ },
+ };
+}
diff --git a/lib/recognition/skiaDecoder.ts b/lib/recognition/skiaDecoder.ts
new file mode 100644
index 0000000..5053d1d
--- /dev/null
+++ b/lib/recognition/skiaDecoder.ts
@@ -0,0 +1,121 @@
+/**
+ * Skia-based image decoder for getting RGBA pixel data.
+ * Uses react-native-skia to decode images and extract pixel buffers.
+ */
+
+import {
+ Skia,
+ useImage,
+ SkImage,
+} from "@shopify/react-native-skia";
+
+/**
+ * Decode a base64 PNG/JPEG image and return RGBA pixel data.
+ *
+ * @param base64 - Base64 encoded image data (without data URI prefix)
+ * @returns RGBA pixel data with dimensions
+ */
+export function decodeImageBase64(base64: string): {
+ pixels: Uint8Array;
+ width: number;
+ height: number;
+} | null {
+ try {
+ // Decode base64 to data
+ const data = Skia.Data.fromBase64(base64);
+ if (!data) return null;
+
+ // Create image from data
+ const image = Skia.Image.MakeImageFromEncoded(data);
+ if (!image) return null;
+
+ const width = image.width();
+ const height = image.height();
+
+ // Read pixels from the image
+ // Note: Skia images are in RGBA format
+ const pixels = image.readPixels(0, 0, {
+ width,
+ height,
+ colorType: 4, // RGBA_8888
+ alphaType: 1, // Unpremultiplied
+ });
+
+ if (!pixels) return null;
+
+ return {
+ pixels: new Uint8Array(pixels),
+ width,
+ height,
+ };
+ } catch (error) {
+ console.error("[SkiaDecoder] Failed to decode image:", error);
+ return null;
+ }
+}
+
+/**
+ * Decode an image from a file URI and return RGBA pixel data.
+ *
+ * @param uri - File URI (e.g., file:///path/to/image.png)
+ * @returns Promise with RGBA pixel data
+ */
+export async function decodeImageFromUri(uri: string): Promise<{
+ pixels: Uint8Array;
+ width: number;
+ height: number;
+} | null> {
+ try {
+ // Fetch the image data
+ const response = await fetch(uri);
+ const arrayBuffer = await response.arrayBuffer();
+ const base64 = arrayBufferToBase64(arrayBuffer);
+
+ return decodeImageBase64(base64);
+ } catch (error) {
+ console.error("[SkiaDecoder] Failed to load image from URI:", error);
+ return null;
+ }
+}
+
+/**
+ * Convert ArrayBuffer to base64 string.
+ */
+function arrayBufferToBase64(buffer: ArrayBuffer): string {
+ let binary = "";
+ const bytes = new Uint8Array(buffer);
+ const len = bytes.byteLength;
+ for (let i = 0; i < len; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return btoa(binary);
+}
+
+/**
+ * React hook to decode an image from URI.
+ * Uses Skia's useImage hook for caching.
+ */
+export function useDecodedImage(uri: string | null) {
+ const skiaImage = useImage(uri);
+
+ if (!skiaImage) {
+ return { loading: true, pixels: null, width: 0, height: 0 };
+ }
+
+ const width = skiaImage.width();
+ const height = skiaImage.height();
+
+ const pixels = skiaImage.readPixels(0, 0, {
+ width,
+ height,
+ colorType: 4, // RGBA_8888
+ alphaType: 1, // Unpremultiplied
+ });
+
+ return {
+ loading: false,
+ pixels: pixels ? new Uint8Array(pixels) : null,
+ width,
+ height,
+ };
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..281ddf5
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,10536 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "app",
+ "version": "1.0.0",
+ "dependencies": {
+ "@auth/core": "^0.37.4",
+ "@convex-dev/auth": "^0.0.90",
+ "@expo/vector-icons": "^15.0.3",
+ "@react-navigation/native": "^7.1.8",
+ "@shopify/react-native-skia": "^2.4.18",
+ "convex": "^1.31.7",
+ "expo": "~54.0.33",
+ "expo-auth-session": "^7.0.10",
+ "expo-camera": "^17.0.10",
+ "expo-constants": "~18.0.13",
+ "expo-crypto": "^15.0.8",
+ "expo-font": "~14.0.11",
+ "expo-image-manipulator": "^14.0.8",
+ "expo-linking": "~8.0.11",
+ "expo-router": "~6.0.23",
+ "expo-secure-store": "^15.0.8",
+ "expo-splash-screen": "~31.0.13",
+ "expo-sqlite": "^16.0.10",
+ "expo-status-bar": "~3.0.9",
+ "expo-web-browser": "~15.0.10",
+ "react": "19.1.0",
+ "react-dom": "19.1.0",
+ "react-native": "0.81.5",
+ "react-native-fast-opencv": "^0.4.7",
+ "react-native-reanimated": "~4.1.1",
+ "react-native-safe-area-context": "~5.6.0",
+ "react-native-screens": "~4.16.0",
+ "react-native-vision-camera": "^4.7.3",
+ "react-native-web": "~0.21.0",
+ "react-native-worklets": "0.5.1",
+ "react-native-worklets-core": "^1.6.2"
+ },
+ "devDependencies": {
+ "@types/better-sqlite3": "^7.6.12",
+ "@types/node": "^22.10.0",
+ "@types/react": "~19.1.0",
+ "better-sqlite3": "^11.6.0",
+ "concurrently": "^9.1.0",
+ "dotenv": "^16.4.7",
+ "react-test-renderer": "19.1.0",
+ "tsx": "^4.19.2",
+ "typescript": "~5.9.2"
+ }
+ },
+ "node_modules/@0no-co/graphql.web": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz",
+ "integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
+ },
+ "peerDependenciesMeta": {
+ "graphql": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@auth/core": {
+ "version": "0.37.4",
+ "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.4.tgz",
+ "integrity": "sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==",
+ "license": "ISC",
+ "dependencies": {
+ "@panva/hkdf": "^1.2.1",
+ "jose": "^5.9.6",
+ "oauth4webapi": "^3.1.1",
+ "preact": "10.24.3",
+ "preact-render-to-string": "6.5.11"
+ },
+ "peerDependencies": {
+ "@simplewebauthn/browser": "^9.0.1",
+ "@simplewebauthn/server": "^9.0.2",
+ "nodemailer": "^6.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@simplewebauthn/browser": {
+ "optional": true
+ },
+ "@simplewebauthn/server": {
+ "optional": true
+ },
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
+ "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/traverse": "^7.28.6",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-regexp-features-plugin": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz",
+ "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "regexpu-core": "^6.3.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz",
+ "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "debug": "^4.4.3",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.22.11"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
+ "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
+ "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-remap-async-to-generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz",
+ "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-wrap-function": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
+ "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
+ "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-wrap-function": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz",
+ "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz",
+ "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz",
+ "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-syntax-decorators": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-export-default-from": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz",
+ "integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz",
+ "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-export-default-from": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz",
+ "integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-flow": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz",
+ "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
+ "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-generator-functions": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz",
+ "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-remap-async-to-generator": "^7.27.1",
+ "@babel/traverse": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-to-generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz",
+ "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-remap-async-to-generator": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoping": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz",
+ "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz",
+ "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-static-block": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz",
+ "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.12.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-classes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz",
+ "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-replace-supers": "^7.28.6",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-computed-properties": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz",
+ "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/template": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
+ "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-export-namespace-from": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz",
+ "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-flow-strip-types": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz",
+ "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-syntax-flow": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-for-of": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz",
+ "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-function-name": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz",
+ "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz",
+ "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz",
+ "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz",
+ "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz",
+ "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz",
+ "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-numeric-separator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz",
+ "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-rest-spread": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz",
+ "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-transform-destructuring": "^7.28.5",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz",
+ "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz",
+ "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-parameters": {
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz",
+ "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-methods": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz",
+ "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-property-in-object": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz",
+ "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-display-name": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz",
+ "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
+ "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-syntax-jsx": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-development": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
+ "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-pure-annotations": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz",
+ "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regenerator": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz",
+ "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-runtime": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz",
+ "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "babel-plugin-polyfill-corejs2": "^0.4.14",
+ "babel-plugin-polyfill-corejs3": "^0.13.0",
+ "babel-plugin-polyfill-regenerator": "^0.6.5",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-shorthand-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
+ "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-spread": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz",
+ "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-sticky-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz",
+ "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-template-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
+ "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
+ "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
+ "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-react": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz",
+ "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-transform-react-display-name": "^7.28.0",
+ "@babel/plugin-transform-react-jsx": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-development": "^7.27.1",
+ "@babel/plugin-transform-react-pure-annotations": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-typescript": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
+ "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-typescript": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse--for-generate-function-map": {
+ "name": "@babel/traverse",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@convex-dev/auth": {
+ "version": "0.0.90",
+ "resolved": "https://registry.npmjs.org/@convex-dev/auth/-/auth-0.0.90.tgz",
+ "integrity": "sha512-aqw88EB042HvnaF4wcf/f/wTocmT2Bus2VDQRuV79cM0+8kORM0ICK/ByZ6XsHgQ9qr6TmidNbXm6QAgndrdpQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@oslojs/crypto": "^1.0.1",
+ "@oslojs/encoding": "^1.1.0",
+ "cookie": "^1.0.1",
+ "is-network-error": "^1.1.0",
+ "jose": "^5.2.2",
+ "jwt-decode": "^4.0.0",
+ "lucia": "^3.2.0",
+ "oauth4webapi": "^3.1.2",
+ "path-to-regexp": "^6.3.0",
+ "server-only": "^0.0.1"
+ },
+ "bin": {
+ "auth": "dist/bin.cjs"
+ },
+ "peerDependencies": {
+ "@auth/core": "^0.37.0",
+ "convex": "^1.17.0",
+ "react": "^18.2.0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz",
+ "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz",
+ "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz",
+ "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz",
+ "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz",
+ "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz",
+ "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz",
+ "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz",
+ "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz",
+ "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz",
+ "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz",
+ "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz",
+ "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz",
+ "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz",
+ "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz",
+ "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz",
+ "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz",
+ "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz",
+ "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz",
+ "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz",
+ "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz",
+ "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz",
+ "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz",
+ "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz",
+ "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz",
+ "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz",
+ "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@expo/code-signing-certificates": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz",
+ "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==",
+ "license": "MIT",
+ "dependencies": {
+ "node-forge": "^1.3.3"
+ }
+ },
+ "node_modules/@expo/config": {
+ "version": "12.0.13",
+ "resolved": "https://registry.npmjs.org/@expo/config/-/config-12.0.13.tgz",
+ "integrity": "sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "~7.10.4",
+ "@expo/config-plugins": "~54.0.4",
+ "@expo/config-types": "^54.0.10",
+ "@expo/json-file": "^10.0.8",
+ "deepmerge": "^4.3.1",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "require-from-string": "^2.0.2",
+ "resolve-from": "^5.0.0",
+ "resolve-workspace-root": "^2.0.0",
+ "semver": "^7.6.0",
+ "slugify": "^1.3.4",
+ "sucrase": "~3.35.1"
+ }
+ },
+ "node_modules/@expo/config-plugins": {
+ "version": "54.0.4",
+ "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-54.0.4.tgz",
+ "integrity": "sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config-types": "^54.0.10",
+ "@expo/json-file": "~10.0.8",
+ "@expo/plist": "^0.4.8",
+ "@expo/sdk-runtime-versions": "^1.0.0",
+ "chalk": "^4.1.2",
+ "debug": "^4.3.5",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.5.4",
+ "slash": "^3.0.0",
+ "slugify": "^1.6.6",
+ "xcode": "^3.0.1",
+ "xml2js": "0.6.0"
+ }
+ },
+ "node_modules/@expo/config-plugins/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@expo/config-types": {
+ "version": "54.0.10",
+ "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-54.0.10.tgz",
+ "integrity": "sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA==",
+ "license": "MIT"
+ },
+ "node_modules/@expo/config/node_modules/@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "node_modules/@expo/config/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@expo/devcert": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz",
+ "integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/sudo-prompt": "^9.3.1",
+ "debug": "^3.1.0"
+ }
+ },
+ "node_modules/@expo/devcert/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/@expo/devtools": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-0.1.8.tgz",
+ "integrity": "sha512-SVLxbuanDjJPgc0sy3EfXUMLb/tXzp6XIHkhtPVmTWJAp+FOr6+5SeiCfJrCzZFet0Ifyke2vX3sFcKwEvCXwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@expo/env": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.0.8.tgz",
+ "integrity": "sha512-5VQD6GT8HIMRaSaB5JFtOXuvfDVU80YtZIuUT/GDhUF782usIXY13Tn3IdDz1Tm/lqA9qnRZQ1BF4t7LlvdJPA==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "debug": "^4.3.4",
+ "dotenv": "~16.4.5",
+ "dotenv-expand": "~11.0.6",
+ "getenv": "^2.0.0"
+ }
+ },
+ "node_modules/@expo/fingerprint": {
+ "version": "0.15.4",
+ "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.15.4.tgz",
+ "integrity": "sha512-eYlxcrGdR2/j2M6pEDXo9zU9KXXF1vhP+V+Tl+lyY+bU8lnzrN6c637mz6Ye3em2ANy8hhUR03Raf8VsT9Ogng==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/spawn-async": "^1.7.2",
+ "arg": "^5.0.2",
+ "chalk": "^4.1.2",
+ "debug": "^4.3.4",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "ignore": "^5.3.1",
+ "minimatch": "^9.0.0",
+ "p-limit": "^3.1.0",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.6.0"
+ },
+ "bin": {
+ "fingerprint": "bin/cli.js"
+ }
+ },
+ "node_modules/@expo/fingerprint/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@expo/image-utils": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.8.tgz",
+ "integrity": "sha512-HHHaG4J4nKjTtVa1GG9PCh763xlETScfEyNxxOvfTRr8IKPJckjTyqSLEtdJoFNJ1vqiABEjW7tqGhqGibZLeA==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/spawn-async": "^1.7.2",
+ "chalk": "^4.0.0",
+ "getenv": "^2.0.0",
+ "jimp-compact": "0.16.1",
+ "parse-png": "^2.1.0",
+ "resolve-from": "^5.0.0",
+ "resolve-global": "^1.0.0",
+ "semver": "^7.6.0",
+ "temp-dir": "~2.0.0",
+ "unique-string": "~2.0.0"
+ }
+ },
+ "node_modules/@expo/image-utils/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@expo/json-file": {
+ "version": "10.0.8",
+ "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.8.tgz",
+ "integrity": "sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "~7.10.4",
+ "json5": "^2.2.3"
+ }
+ },
+ "node_modules/@expo/json-file/node_modules/@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "node_modules/@expo/metro": {
+ "version": "54.2.0",
+ "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz",
+ "integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==",
+ "license": "MIT",
+ "dependencies": {
+ "metro": "0.83.3",
+ "metro-babel-transformer": "0.83.3",
+ "metro-cache": "0.83.3",
+ "metro-cache-key": "0.83.3",
+ "metro-config": "0.83.3",
+ "metro-core": "0.83.3",
+ "metro-file-map": "0.83.3",
+ "metro-minify-terser": "0.83.3",
+ "metro-resolver": "0.83.3",
+ "metro-runtime": "0.83.3",
+ "metro-source-map": "0.83.3",
+ "metro-symbolicate": "0.83.3",
+ "metro-transform-plugins": "0.83.3",
+ "metro-transform-worker": "0.83.3"
+ }
+ },
+ "node_modules/@expo/metro-config": {
+ "version": "54.0.14",
+ "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-54.0.14.tgz",
+ "integrity": "sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.20.0",
+ "@babel/core": "^7.20.0",
+ "@babel/generator": "^7.20.5",
+ "@expo/config": "~12.0.13",
+ "@expo/env": "~2.0.8",
+ "@expo/json-file": "~10.0.8",
+ "@expo/metro": "~54.2.0",
+ "@expo/spawn-async": "^1.7.2",
+ "browserslist": "^4.25.0",
+ "chalk": "^4.1.0",
+ "debug": "^4.3.2",
+ "dotenv": "~16.4.5",
+ "dotenv-expand": "~11.0.6",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "hermes-parser": "^0.29.1",
+ "jsc-safe-url": "^0.2.4",
+ "lightningcss": "^1.30.1",
+ "minimatch": "^9.0.0",
+ "postcss": "~8.4.32",
+ "resolve-from": "^5.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@expo/metro-runtime": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-6.1.2.tgz",
+ "integrity": "sha512-nvM+Qv45QH7pmYvP8JB1G8JpScrWND3KrMA6ZKe62cwwNiX/BjHU28Ear0v/4bQWXlOY0mv6B8CDIm8JxXde9g==",
+ "license": "MIT",
+ "dependencies": {
+ "anser": "^1.4.9",
+ "pretty-format": "^29.7.0",
+ "stacktrace-parser": "^0.1.10",
+ "whatwg-fetch": "^3.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-dom": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@expo/osascript": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.3.8.tgz",
+ "integrity": "sha512-/TuOZvSG7Nn0I8c+FcEaoHeBO07yu6vwDgk7rZVvAXoeAK5rkA09jRyjYsZo+0tMEFaToBeywA6pj50Mb3ny9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/spawn-async": "^1.7.2",
+ "exec-async": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@expo/package-manager": {
+ "version": "1.9.10",
+ "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.9.10.tgz",
+ "integrity": "sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/json-file": "^10.0.8",
+ "@expo/spawn-async": "^1.7.2",
+ "chalk": "^4.0.0",
+ "npm-package-arg": "^11.0.0",
+ "ora": "^3.4.0",
+ "resolve-workspace-root": "^2.0.0"
+ }
+ },
+ "node_modules/@expo/plist": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.4.8.tgz",
+ "integrity": "sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.2.3",
+ "xmlbuilder": "^15.1.1"
+ }
+ },
+ "node_modules/@expo/prebuild-config": {
+ "version": "54.0.8",
+ "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-54.0.8.tgz",
+ "integrity": "sha512-EA7N4dloty2t5Rde+HP0IEE+nkAQiu4A/+QGZGT9mFnZ5KKjPPkqSyYcRvP5bhQE10D+tvz6X0ngZpulbMdbsg==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config": "~12.0.13",
+ "@expo/config-plugins": "~54.0.4",
+ "@expo/config-types": "^54.0.10",
+ "@expo/image-utils": "^0.8.8",
+ "@expo/json-file": "^10.0.8",
+ "@react-native/normalize-colors": "0.81.5",
+ "debug": "^4.3.1",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.6.0",
+ "xml2js": "0.6.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/@expo/prebuild-config/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@expo/schema-utils": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-0.1.8.tgz",
+ "integrity": "sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A==",
+ "license": "MIT"
+ },
+ "node_modules/@expo/sdk-runtime-versions": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz",
+ "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==",
+ "license": "MIT"
+ },
+ "node_modules/@expo/spawn-async": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz",
+ "integrity": "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@expo/sudo-prompt": {
+ "version": "9.3.2",
+ "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz",
+ "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==",
+ "license": "MIT"
+ },
+ "node_modules/@expo/vector-icons": {
+ "version": "15.0.3",
+ "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz",
+ "integrity": "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo-font": ">=14.0.4",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/@expo/ws-tunnel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz",
+ "integrity": "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==",
+ "license": "MIT"
+ },
+ "node_modules/@expo/xcpretty": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.0.tgz",
+ "integrity": "sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/code-frame": "^7.20.0",
+ "chalk": "^4.1.0",
+ "js-yaml": "^4.1.0"
+ },
+ "bin": {
+ "excpretty": "build/cli.js"
+ }
+ },
+ "node_modules/@expo/xcpretty/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/@expo/xcpretty/node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@isaacs/balanced-match": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
+ "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/brace-expansion": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
+ "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/balanced-match": "^4.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@isaacs/ttlcache": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz",
+ "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/create-cache-key-function": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz",
+ "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@oslojs/asn1": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@oslojs/asn1/-/asn1-1.0.0.tgz",
+ "integrity": "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@oslojs/binary": "1.0.0"
+ }
+ },
+ "node_modules/@oslojs/binary": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@oslojs/binary/-/binary-1.0.0.tgz",
+ "integrity": "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==",
+ "license": "MIT"
+ },
+ "node_modules/@oslojs/crypto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@oslojs/crypto/-/crypto-1.0.1.tgz",
+ "integrity": "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@oslojs/asn1": "1.0.0",
+ "@oslojs/binary": "1.0.0"
+ }
+ },
+ "node_modules/@oslojs/encoding": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz",
+ "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==",
+ "license": "MIT"
+ },
+ "node_modules/@panva/hkdf": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
+ "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-native/assets-registry": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz",
+ "integrity": "sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.4"
+ }
+ },
+ "node_modules/@react-native/babel-plugin-codegen": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.81.5.tgz",
+ "integrity": "sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.3",
+ "@react-native/codegen": "0.81.5"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ }
+ },
+ "node_modules/@react-native/babel-preset": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.81.5.tgz",
+ "integrity": "sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.25.2",
+ "@babel/plugin-proposal-export-default-from": "^7.24.7",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-syntax-export-default-from": "^7.24.7",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-transform-arrow-functions": "^7.24.7",
+ "@babel/plugin-transform-async-generator-functions": "^7.25.4",
+ "@babel/plugin-transform-async-to-generator": "^7.24.7",
+ "@babel/plugin-transform-block-scoping": "^7.25.0",
+ "@babel/plugin-transform-class-properties": "^7.25.4",
+ "@babel/plugin-transform-classes": "^7.25.4",
+ "@babel/plugin-transform-computed-properties": "^7.24.7",
+ "@babel/plugin-transform-destructuring": "^7.24.8",
+ "@babel/plugin-transform-flow-strip-types": "^7.25.2",
+ "@babel/plugin-transform-for-of": "^7.24.7",
+ "@babel/plugin-transform-function-name": "^7.25.1",
+ "@babel/plugin-transform-literals": "^7.25.2",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
+ "@babel/plugin-transform-modules-commonjs": "^7.24.8",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
+ "@babel/plugin-transform-numeric-separator": "^7.24.7",
+ "@babel/plugin-transform-object-rest-spread": "^7.24.7",
+ "@babel/plugin-transform-optional-catch-binding": "^7.24.7",
+ "@babel/plugin-transform-optional-chaining": "^7.24.8",
+ "@babel/plugin-transform-parameters": "^7.24.7",
+ "@babel/plugin-transform-private-methods": "^7.24.7",
+ "@babel/plugin-transform-private-property-in-object": "^7.24.7",
+ "@babel/plugin-transform-react-display-name": "^7.24.7",
+ "@babel/plugin-transform-react-jsx": "^7.25.2",
+ "@babel/plugin-transform-react-jsx-self": "^7.24.7",
+ "@babel/plugin-transform-react-jsx-source": "^7.24.7",
+ "@babel/plugin-transform-regenerator": "^7.24.7",
+ "@babel/plugin-transform-runtime": "^7.24.7",
+ "@babel/plugin-transform-shorthand-properties": "^7.24.7",
+ "@babel/plugin-transform-spread": "^7.24.7",
+ "@babel/plugin-transform-sticky-regex": "^7.24.7",
+ "@babel/plugin-transform-typescript": "^7.25.2",
+ "@babel/plugin-transform-unicode-regex": "^7.24.7",
+ "@babel/template": "^7.25.0",
+ "@react-native/babel-plugin-codegen": "0.81.5",
+ "babel-plugin-syntax-hermes-parser": "0.29.1",
+ "babel-plugin-transform-flow-enums": "^0.0.2",
+ "react-refresh": "^0.14.0"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "*"
+ }
+ },
+ "node_modules/@react-native/codegen": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.81.5.tgz",
+ "integrity": "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.25.2",
+ "@babel/parser": "^7.25.3",
+ "glob": "^7.1.1",
+ "hermes-parser": "0.29.1",
+ "invariant": "^2.2.4",
+ "nullthrows": "^1.1.1",
+ "yargs": "^17.6.2"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "*"
+ }
+ },
+ "node_modules/@react-native/codegen/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@react-native/codegen/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@react-native/codegen/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@react-native/community-cli-plugin": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.81.5.tgz",
+ "integrity": "sha512-yWRlmEOtcyvSZ4+OvqPabt+NS36vg0K/WADTQLhrYrm9qdZSuXmq8PmdJWz/68wAqKQ+4KTILiq2kjRQwnyhQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-native/dev-middleware": "0.81.5",
+ "debug": "^4.4.0",
+ "invariant": "^2.2.4",
+ "metro": "^0.83.1",
+ "metro-config": "^0.83.1",
+ "metro-core": "^0.83.1",
+ "semver": "^7.1.3"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ },
+ "peerDependencies": {
+ "@react-native-community/cli": "*",
+ "@react-native/metro-config": "*"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-community/cli": {
+ "optional": true
+ },
+ "@react-native/metro-config": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-native/community-cli-plugin/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@react-native/debugger-frontend": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.81.5.tgz",
+ "integrity": "sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 20.19.4"
+ }
+ },
+ "node_modules/@react-native/dev-middleware": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.81.5.tgz",
+ "integrity": "sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/ttlcache": "^1.4.1",
+ "@react-native/debugger-frontend": "0.81.5",
+ "chrome-launcher": "^0.15.2",
+ "chromium-edge-launcher": "^0.2.0",
+ "connect": "^3.6.5",
+ "debug": "^4.4.0",
+ "invariant": "^2.2.4",
+ "nullthrows": "^1.1.1",
+ "open": "^7.0.3",
+ "serve-static": "^1.16.2",
+ "ws": "^6.2.3"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ }
+ },
+ "node_modules/@react-native/dev-middleware/node_modules/ws": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
+ "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
+ "license": "MIT",
+ "dependencies": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "node_modules/@react-native/gradle-plugin": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.81.5.tgz",
+ "integrity": "sha512-hORRlNBj+ReNMLo9jme3yQ6JQf4GZpVEBLxmTXGGlIL78MAezDZr5/uq9dwElSbcGmLEgeiax6e174Fie6qPLg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.4"
+ }
+ },
+ "node_modules/@react-native/js-polyfills": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.81.5.tgz",
+ "integrity": "sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.4"
+ }
+ },
+ "node_modules/@react-native/normalize-colors": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.5.tgz",
+ "integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==",
+ "license": "MIT"
+ },
+ "node_modules/@react-navigation/bottom-tabs": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.12.0.tgz",
+ "integrity": "sha512-/GtOfVWRligHG0mvX39I1FGdUWeWl0GVF2okEziQSQj0bOTrLIt7y44C3r/aCLkEpTVltCPGM3swqGTH3UfRCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/elements": "^2.9.5",
+ "color": "^4.2.3",
+ "sf-symbols-typescript": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@react-navigation/native": "^7.1.28",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0",
+ "react-native-screens": ">= 4.0.0"
+ }
+ },
+ "node_modules/@react-navigation/core": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.14.0.tgz",
+ "integrity": "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/routers": "^7.5.3",
+ "escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
+ "nanoid": "^3.3.11",
+ "query-string": "^7.1.3",
+ "react-is": "^19.1.0",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "react": ">= 18.2.0"
+ }
+ },
+ "node_modules/@react-navigation/elements": {
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.5.tgz",
+ "integrity": "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g==",
+ "license": "MIT",
+ "dependencies": {
+ "color": "^4.2.3",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@react-native-masked-view/masked-view": ">= 0.2.0",
+ "@react-navigation/native": "^7.1.28",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-masked-view/masked-view": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-navigation/native": {
+ "version": "7.1.28",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.28.tgz",
+ "integrity": "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/core": "^7.14.0",
+ "escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
+ "nanoid": "^3.3.11",
+ "use-latest-callback": "^0.2.4"
+ },
+ "peerDependencies": {
+ "react": ">= 18.2.0",
+ "react-native": "*"
+ }
+ },
+ "node_modules/@react-navigation/native-stack": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.12.0.tgz",
+ "integrity": "sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/elements": "^2.9.5",
+ "color": "^4.2.3",
+ "sf-symbols-typescript": "^2.1.0",
+ "warn-once": "^0.1.1"
+ },
+ "peerDependencies": {
+ "@react-navigation/native": "^7.1.28",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0",
+ "react-native-screens": ">= 4.0.0"
+ }
+ },
+ "node_modules/@react-navigation/routers": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
+ "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11"
+ }
+ },
+ "node_modules/@shopify/react-native-skia": {
+ "version": "2.4.18",
+ "resolved": "https://registry.npmjs.org/@shopify/react-native-skia/-/react-native-skia-2.4.18.tgz",
+ "integrity": "sha512-/AB/mvb2dGSQVIJTxyG9ZMFn2PjwWlVcFxHU4TV+K25LCU49ntJXl4xda2ghXxdENMggKgO9R5pA+P/AQ0UUrA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "canvaskit-wasm": "0.40.0",
+ "react-reconciler": "0.31.0"
+ },
+ "bin": {
+ "setup-skia-web": "scripts/setup-canvaskit.js"
+ },
+ "peerDependencies": {
+ "react": ">=19.0",
+ "react-native": ">=0.78",
+ "react-native-reanimated": ">=3.19.1"
+ },
+ "peerDependenciesMeta": {
+ "react-native": {
+ "optional": true
+ },
+ "react-native-reanimated": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.10",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
+ "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
+ "license": "MIT"
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/better-sqlite3": {
+ "version": "7.6.13",
+ "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
+ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.10",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.10.tgz",
+ "integrity": "sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.17",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz",
+ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@urql/core": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz",
+ "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==",
+ "license": "MIT",
+ "dependencies": {
+ "@0no-co/graphql.web": "^1.0.13",
+ "wonka": "^6.3.2"
+ }
+ },
+ "node_modules/@urql/exchange-retry": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz",
+ "integrity": "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@urql/core": "^5.1.2",
+ "wonka": "^6.3.2"
+ },
+ "peerDependencies": {
+ "@urql/core": "^5.0.0"
+ }
+ },
+ "node_modules/@webgpu/types": {
+ "version": "0.1.21",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.21.tgz",
+ "integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.11",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
+ "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/anser": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
+ "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==",
+ "license": "MIT"
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "license": "MIT"
+ },
+ "node_modules/async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+ "license": "MIT"
+ },
+ "node_modules/await-lock": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz",
+ "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
+ "license": "MIT"
+ },
+ "node_modules/babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs2": {
+ "version": "0.4.15",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz",
+ "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-define-polyfill-provider": "^0.6.6",
+ "semver": "^6.3.1"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs3": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz",
+ "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.5",
+ "core-js-compat": "^3.43.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-regenerator": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz",
+ "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.6"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-react-compiler": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz",
+ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ }
+ },
+ "node_modules/babel-plugin-react-native-web": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz",
+ "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==",
+ "license": "MIT"
+ },
+ "node_modules/babel-plugin-syntax-hermes-parser": {
+ "version": "0.29.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.29.1.tgz",
+ "integrity": "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==",
+ "license": "MIT",
+ "dependencies": {
+ "hermes-parser": "0.29.1"
+ }
+ },
+ "node_modules/babel-plugin-transform-flow-enums": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz",
+ "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-flow": "^7.12.1"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-expo": {
+ "version": "54.0.10",
+ "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz",
+ "integrity": "sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/plugin-proposal-decorators": "^7.12.9",
+ "@babel/plugin-proposal-export-default-from": "^7.24.7",
+ "@babel/plugin-syntax-export-default-from": "^7.24.7",
+ "@babel/plugin-transform-class-static-block": "^7.27.1",
+ "@babel/plugin-transform-export-namespace-from": "^7.25.9",
+ "@babel/plugin-transform-flow-strip-types": "^7.25.2",
+ "@babel/plugin-transform-modules-commonjs": "^7.24.8",
+ "@babel/plugin-transform-object-rest-spread": "^7.24.7",
+ "@babel/plugin-transform-parameters": "^7.24.7",
+ "@babel/plugin-transform-private-methods": "^7.24.7",
+ "@babel/plugin-transform-private-property-in-object": "^7.24.7",
+ "@babel/plugin-transform-runtime": "^7.24.7",
+ "@babel/preset-react": "^7.22.15",
+ "@babel/preset-typescript": "^7.23.0",
+ "@react-native/babel-preset": "0.81.5",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "babel-plugin-react-native-web": "~0.21.0",
+ "babel-plugin-syntax-hermes-parser": "^0.29.1",
+ "babel-plugin-transform-flow-enums": "^0.0.2",
+ "debug": "^4.3.4",
+ "resolve-from": "^5.0.0"
+ },
+ "peerDependencies": {
+ "@babel/runtime": "^7.20.0",
+ "expo": "*",
+ "react-refresh": ">=0.14.0 <1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/runtime": {
+ "optional": true
+ },
+ "expo": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/better-opn": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz",
+ "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "open": "^8.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/better-opn/node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/better-sqlite3": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
+ "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ }
+ },
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bplist-creator": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
+ "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==",
+ "license": "MIT",
+ "dependencies": {
+ "stream-buffers": "2.2.x"
+ }
+ },
+ "node_modules/bplist-parser": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz",
+ "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==",
+ "license": "MIT",
+ "dependencies": {
+ "big-integer": "1.6.x"
+ },
+ "engines": {
+ "node": ">= 5.10.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "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"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001769",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
+ "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/canvaskit-wasm": {
+ "version": "0.40.0",
+ "resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.40.0.tgz",
+ "integrity": "sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@webgpu/types": "0.1.21"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chrome-launcher": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz",
+ "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/node": "*",
+ "escape-string-regexp": "^4.0.0",
+ "is-wsl": "^2.2.0",
+ "lighthouse-logger": "^1.0.0"
+ },
+ "bin": {
+ "print-chrome-path": "bin/print-chrome-path.js"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ }
+ },
+ "node_modules/chromium-edge-launcher": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz",
+ "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/node": "*",
+ "escape-string-regexp": "^4.0.0",
+ "is-wsl": "^2.2.0",
+ "lighthouse-logger": "^1.0.0",
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+ "license": "MIT"
+ },
+ "node_modules/cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": ">= 1.43.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "compressible": "~2.0.18",
+ "debug": "2.6.9",
+ "negotiator": "~0.6.4",
+ "on-headers": "~1.1.0",
+ "safe-buffer": "5.2.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/compression/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/compression/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/compression/node_modules/negotiator": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "license": "MIT"
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
+ "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.8.3",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/connect": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+ "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.2",
+ "parseurl": "~1.3.3",
+ "utils-merge": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/connect/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/connect/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/convex": {
+ "version": "1.31.7",
+ "resolved": "https://registry.npmjs.org/convex/-/convex-1.31.7.tgz",
+ "integrity": "sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esbuild": "0.27.0",
+ "prettier": "^3.0.0"
+ },
+ "bin": {
+ "convex": "bin/main.js"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=7.0.0"
+ },
+ "peerDependencies": {
+ "@auth0/auth0-react": "^2.0.1",
+ "@clerk/clerk-react": "^4.12.8 || ^5.0.0",
+ "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@auth0/auth0-react": {
+ "optional": true
+ },
+ "@clerk/clerk-react": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.48.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz",
+ "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cross-fetch": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
+ "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "node-fetch": "^2.7.0"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypto-random-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/css-in-js-utils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz",
+ "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==",
+ "license": "MIT",
+ "dependencies": {
+ "hyphenate-style-name": "^1.0.3"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-uri-component": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.7",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
+ "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dotenv": "^16.4.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/env-editor": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz",
+ "integrity": "sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/error-stack-parser": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
+ "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "stackframe": "^1.3.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
+ "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.0",
+ "@esbuild/android-arm": "0.27.0",
+ "@esbuild/android-arm64": "0.27.0",
+ "@esbuild/android-x64": "0.27.0",
+ "@esbuild/darwin-arm64": "0.27.0",
+ "@esbuild/darwin-x64": "0.27.0",
+ "@esbuild/freebsd-arm64": "0.27.0",
+ "@esbuild/freebsd-x64": "0.27.0",
+ "@esbuild/linux-arm": "0.27.0",
+ "@esbuild/linux-arm64": "0.27.0",
+ "@esbuild/linux-ia32": "0.27.0",
+ "@esbuild/linux-loong64": "0.27.0",
+ "@esbuild/linux-mips64el": "0.27.0",
+ "@esbuild/linux-ppc64": "0.27.0",
+ "@esbuild/linux-riscv64": "0.27.0",
+ "@esbuild/linux-s390x": "0.27.0",
+ "@esbuild/linux-x64": "0.27.0",
+ "@esbuild/netbsd-arm64": "0.27.0",
+ "@esbuild/netbsd-x64": "0.27.0",
+ "@esbuild/openbsd-arm64": "0.27.0",
+ "@esbuild/openbsd-x64": "0.27.0",
+ "@esbuild/openharmony-arm64": "0.27.0",
+ "@esbuild/sunos-x64": "0.27.0",
+ "@esbuild/win32-arm64": "0.27.0",
+ "@esbuild/win32-ia32": "0.27.0",
+ "@esbuild/win32-x64": "0.27.0"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/exec-async": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz",
+ "integrity": "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==",
+ "license": "MIT"
+ },
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "dev": true,
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/expo": {
+ "version": "54.0.33",
+ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz",
+ "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.0",
+ "@expo/cli": "54.0.23",
+ "@expo/config": "~12.0.13",
+ "@expo/config-plugins": "~54.0.4",
+ "@expo/devtools": "0.1.8",
+ "@expo/fingerprint": "0.15.4",
+ "@expo/metro": "~54.2.0",
+ "@expo/metro-config": "54.0.14",
+ "@expo/vector-icons": "^15.0.3",
+ "@ungap/structured-clone": "^1.3.0",
+ "babel-preset-expo": "~54.0.10",
+ "expo-asset": "~12.0.12",
+ "expo-constants": "~18.0.13",
+ "expo-file-system": "~19.0.21",
+ "expo-font": "~14.0.11",
+ "expo-keep-awake": "~15.0.8",
+ "expo-modules-autolinking": "3.0.24",
+ "expo-modules-core": "3.0.29",
+ "pretty-format": "^29.7.0",
+ "react-refresh": "^0.14.2",
+ "whatwg-url-without-unicode": "8.0.0-3"
+ },
+ "bin": {
+ "expo": "bin/cli",
+ "expo-modules-autolinking": "bin/autolinking",
+ "fingerprint": "bin/fingerprint"
+ },
+ "peerDependencies": {
+ "@expo/dom-webview": "*",
+ "@expo/metro-runtime": "*",
+ "react": "*",
+ "react-native": "*",
+ "react-native-webview": "*"
+ },
+ "peerDependenciesMeta": {
+ "@expo/dom-webview": {
+ "optional": true
+ },
+ "@expo/metro-runtime": {
+ "optional": true
+ },
+ "react-native-webview": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-asset": {
+ "version": "12.0.12",
+ "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.12.tgz",
+ "integrity": "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/image-utils": "^0.8.8",
+ "expo-constants": "~18.0.12"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-auth-session": {
+ "version": "7.0.10",
+ "resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-7.0.10.tgz",
+ "integrity": "sha512-XDnKkudvhHSKkZfJ+KkodM+anQcrxB71i+h0kKabdLa5YDXTQ81aC38KRc3TMqmnBDHAu0NpfbzEVd9WDFY3Qg==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-application": "~7.0.8",
+ "expo-constants": "~18.0.11",
+ "expo-crypto": "~15.0.8",
+ "expo-linking": "~8.0.10",
+ "expo-web-browser": "~15.0.10",
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-auth-session/node_modules/expo-application": {
+ "version": "7.0.8",
+ "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-7.0.8.tgz",
+ "integrity": "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-camera": {
+ "version": "17.0.10",
+ "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-17.0.10.tgz",
+ "integrity": "sha512-w1RBw83mAGVk4BPPwNrCZyFop0VLiVSRE3c2V9onWbdFwonpRhzmB4drygG8YOUTl1H3wQvALJHyMPTbgsK1Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*",
+ "react-native-web": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-web": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-constants": {
+ "version": "18.0.13",
+ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz",
+ "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config": "~12.0.13",
+ "@expo/env": "~2.0.8"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-crypto": {
+ "version": "15.0.8",
+ "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.8.tgz",
+ "integrity": "sha512-aF7A914TB66WIlTJvl5J6/itejfY78O7dq3ibvFltL9vnTALJ/7LYHvLT4fwmx9yUNS6ekLBtDGWivFWnj2Fcw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-file-system": {
+ "version": "19.0.21",
+ "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz",
+ "integrity": "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-font": {
+ "version": "14.0.11",
+ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz",
+ "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==",
+ "license": "MIT",
+ "dependencies": {
+ "fontfaceobserver": "^2.1.0"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-image-loader": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz",
+ "integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-image-manipulator": {
+ "version": "14.0.8",
+ "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-14.0.8.tgz",
+ "integrity": "sha512-sXsXjm7rIxLWZe0j2A41J/Ph53PpFJRdyzJ3EQ/qetxLUvS2m3K1sP5xy37px43qCf0l79N/i6XgFgenFV36/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-image-loader": "~6.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-keep-awake": {
+ "version": "15.0.8",
+ "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz",
+ "integrity": "sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*"
+ }
+ },
+ "node_modules/expo-linking": {
+ "version": "8.0.11",
+ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz",
+ "integrity": "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-constants": "~18.0.12",
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-modules-autolinking": {
+ "version": "3.0.24",
+ "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz",
+ "integrity": "sha512-TP+6HTwhL7orDvsz2VzauyQlXJcAWyU3ANsZ7JGL4DQu8XaZv/A41ZchbtAYLfozNA2Ya1Hzmhx65hXryBMjaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/spawn-async": "^1.7.2",
+ "chalk": "^4.1.0",
+ "commander": "^7.2.0",
+ "require-from-string": "^2.0.2",
+ "resolve-from": "^5.0.0"
+ },
+ "bin": {
+ "expo-modules-autolinking": "bin/expo-modules-autolinking.js"
+ }
+ },
+ "node_modules/expo-modules-core": {
+ "version": "3.0.29",
+ "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-3.0.29.tgz",
+ "integrity": "sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-router": {
+ "version": "6.0.23",
+ "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-6.0.23.tgz",
+ "integrity": "sha512-qCxVAiCrCyu0npky6azEZ6dJDMt77OmCzEbpF6RbUTlfkaCA417LvY14SBkk0xyGruSxy/7pvJOI6tuThaUVCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/metro-runtime": "^6.1.2",
+ "@expo/schema-utils": "^0.1.8",
+ "@radix-ui/react-slot": "1.2.0",
+ "@radix-ui/react-tabs": "^1.1.12",
+ "@react-navigation/bottom-tabs": "^7.4.0",
+ "@react-navigation/native": "^7.1.8",
+ "@react-navigation/native-stack": "^7.3.16",
+ "client-only": "^0.0.1",
+ "debug": "^4.3.4",
+ "escape-string-regexp": "^4.0.0",
+ "expo-server": "^1.0.5",
+ "fast-deep-equal": "^3.1.3",
+ "invariant": "^2.2.4",
+ "nanoid": "^3.3.8",
+ "query-string": "^7.1.3",
+ "react-fast-compare": "^3.2.2",
+ "react-native-is-edge-to-edge": "^1.1.6",
+ "semver": "~7.6.3",
+ "server-only": "^0.0.1",
+ "sf-symbols-typescript": "^2.1.0",
+ "shallowequal": "^1.1.0",
+ "use-latest-callback": "^0.2.1",
+ "vaul": "^1.1.2"
+ },
+ "peerDependencies": {
+ "@expo/metro-runtime": "^6.1.2",
+ "@react-navigation/drawer": "^7.5.0",
+ "@testing-library/react-native": ">= 12.0.0",
+ "expo": "*",
+ "expo-constants": "^18.0.13",
+ "expo-linking": "^8.0.11",
+ "react": "*",
+ "react-dom": "*",
+ "react-native": "*",
+ "react-native-gesture-handler": "*",
+ "react-native-reanimated": "*",
+ "react-native-safe-area-context": ">= 5.4.0",
+ "react-native-screens": "*",
+ "react-native-web": "*",
+ "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4"
+ },
+ "peerDependenciesMeta": {
+ "@react-navigation/drawer": {
+ "optional": true
+ },
+ "@testing-library/react-native": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native-gesture-handler": {
+ "optional": true
+ },
+ "react-native-reanimated": {
+ "optional": true
+ },
+ "react-native-web": {
+ "optional": true
+ },
+ "react-server-dom-webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/expo-secure-store": {
+ "version": "15.0.8",
+ "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-15.0.8.tgz",
+ "integrity": "sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-server": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.5.tgz",
+ "integrity": "sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.16.0"
+ }
+ },
+ "node_modules/expo-splash-screen": {
+ "version": "31.0.13",
+ "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-31.0.13.tgz",
+ "integrity": "sha512-1epJLC1cDlwwj089R2h8cxaU5uk4ONVAC+vzGiTZH4YARQhL4Stlz1MbR6yAS173GMosvkE6CAeihR7oIbCkDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/prebuild-config": "^54.0.8"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-sqlite": {
+ "version": "16.0.10",
+ "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-16.0.10.tgz",
+ "integrity": "sha512-tUOKxE9TpfneRG3eOfbNfhN9236SJ7IiUnP8gCqU7umd9DtgDGB/5PhYVVfl+U7KskgolgNoB9v9OZ9iwXN8Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "await-lock": "^2.2.2"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-status-bar": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-3.0.9.tgz",
+ "integrity": "sha512-xyYyVg6V1/SSOZWh4Ni3U129XHCnFHBTcUo0dhWtFDrZbNp/duw5AGsQfb2sVeU0gxWHXSY1+5F0jnKYC7WuOw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-native-is-edge-to-edge": "^1.2.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo-web-browser": {
+ "version": "15.0.10",
+ "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.10.tgz",
+ "integrity": "sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/expo/node_modules/@expo/cli": {
+ "version": "54.0.23",
+ "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.23.tgz",
+ "integrity": "sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g==",
+ "license": "MIT",
+ "dependencies": {
+ "@0no-co/graphql.web": "^1.0.8",
+ "@expo/code-signing-certificates": "^0.0.6",
+ "@expo/config": "~12.0.13",
+ "@expo/config-plugins": "~54.0.4",
+ "@expo/devcert": "^1.2.1",
+ "@expo/env": "~2.0.8",
+ "@expo/image-utils": "^0.8.8",
+ "@expo/json-file": "^10.0.8",
+ "@expo/metro": "~54.2.0",
+ "@expo/metro-config": "~54.0.14",
+ "@expo/osascript": "^2.3.8",
+ "@expo/package-manager": "^1.9.10",
+ "@expo/plist": "^0.4.8",
+ "@expo/prebuild-config": "^54.0.8",
+ "@expo/schema-utils": "^0.1.8",
+ "@expo/spawn-async": "^1.7.2",
+ "@expo/ws-tunnel": "^1.0.1",
+ "@expo/xcpretty": "^4.3.0",
+ "@react-native/dev-middleware": "0.81.5",
+ "@urql/core": "^5.0.6",
+ "@urql/exchange-retry": "^1.3.0",
+ "accepts": "^1.3.8",
+ "arg": "^5.0.2",
+ "better-opn": "~3.0.2",
+ "bplist-creator": "0.1.0",
+ "bplist-parser": "^0.3.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.3.0",
+ "compression": "^1.7.4",
+ "connect": "^3.7.0",
+ "debug": "^4.3.4",
+ "env-editor": "^0.4.1",
+ "expo-server": "^1.0.5",
+ "freeport-async": "^2.0.0",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "lan-network": "^0.1.6",
+ "minimatch": "^9.0.0",
+ "node-forge": "^1.3.3",
+ "npm-package-arg": "^11.0.0",
+ "ora": "^3.4.0",
+ "picomatch": "^3.0.1",
+ "pretty-bytes": "^5.6.0",
+ "pretty-format": "^29.7.0",
+ "progress": "^2.0.3",
+ "prompts": "^2.3.2",
+ "qrcode-terminal": "0.11.0",
+ "require-from-string": "^2.0.2",
+ "requireg": "^0.2.2",
+ "resolve": "^1.22.2",
+ "resolve-from": "^5.0.0",
+ "resolve.exports": "^2.0.3",
+ "semver": "^7.6.0",
+ "send": "^0.19.0",
+ "slugify": "^1.3.4",
+ "source-map-support": "~0.5.21",
+ "stacktrace-parser": "^0.1.10",
+ "structured-headers": "^0.4.1",
+ "tar": "^7.5.2",
+ "terminal-link": "^2.1.1",
+ "undici": "^6.18.2",
+ "wrap-ansi": "^7.0.0",
+ "ws": "^8.12.1"
+ },
+ "bin": {
+ "expo-internal": "build/bin/cli"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "expo-router": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "expo-router": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo/node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/expo/node_modules/picomatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
+ "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/expo/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/expo/node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/exponential-backoff": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
+ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "license": "MIT"
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fbjs": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz",
+ "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-fetch": "^3.1.5",
+ "fbjs-css-vars": "^1.0.0",
+ "loose-envify": "^1.0.0",
+ "object-assign": "^4.1.0",
+ "promise": "^7.1.1",
+ "setimmediate": "^1.0.5",
+ "ua-parser-js": "^1.0.35"
+ }
+ },
+ "node_modules/fbjs-css-vars": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
+ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
+ "license": "MIT"
+ },
+ "node_modules/fbjs/node_modules/promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "license": "MIT",
+ "dependencies": {
+ "asap": "~2.0.3"
+ }
+ },
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/filter-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
+ "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/flow-enums-runtime": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz",
+ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==",
+ "license": "MIT"
+ },
+ "node_modules/fontfaceobserver": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
+ "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/freeport-async": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz",
+ "integrity": "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.6",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
+ "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/getenv": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz",
+ "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/glob": {
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz",
+ "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.1.2",
+ "minipass": "^7.1.2",
+ "path-scurry": "^2.0.0"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "10.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz",
+ "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/brace-expansion": "^5.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/global-dirs": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+ "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==",
+ "license": "MIT",
+ "dependencies": {
+ "ini": "^1.3.4"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.29.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.29.1.tgz",
+ "integrity": "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==",
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.29.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.29.1.tgz",
+ "integrity": "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==",
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.29.1"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
+ "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^10.0.1"
+ },
+ "engines": {
+ "node": "^16.14.0 || >=18.0.0"
+ }
+ },
+ "node_modules/hosted-git-info/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-errors/node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/hyphenate-style-name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
+ "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/image-size": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
+ "license": "MIT",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=16.x"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
+ },
+ "node_modules/inline-style-prefixer": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz",
+ "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==",
+ "license": "MIT",
+ "dependencies": {
+ "css-in-js-utils": "^3.1.0"
+ }
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+ "license": "MIT"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-network-error": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
+ "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/jimp-compact": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz",
+ "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==",
+ "license": "MIT"
+ },
+ "node_modules/jose": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsc-safe-url": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz",
+ "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==",
+ "license": "0BSD"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lan-network": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.1.7.tgz",
+ "integrity": "sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==",
+ "license": "MIT",
+ "bin": {
+ "lan-network": "dist/lan-network-cli.js"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lighthouse-logger": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
+ "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^2.6.9",
+ "marky": "^1.2.2"
+ }
+ },
+ "node_modules/lighthouse-logger/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/lighthouse-logger/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/lightningcss": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
+ "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.31.1",
+ "lightningcss-darwin-arm64": "1.31.1",
+ "lightningcss-darwin-x64": "1.31.1",
+ "lightningcss-freebsd-x64": "1.31.1",
+ "lightningcss-linux-arm-gnueabihf": "1.31.1",
+ "lightningcss-linux-arm64-gnu": "1.31.1",
+ "lightningcss-linux-arm64-musl": "1.31.1",
+ "lightningcss-linux-x64-gnu": "1.31.1",
+ "lightningcss-linux-x64-musl": "1.31.1",
+ "lightningcss-win32-arm64-msvc": "1.31.1",
+ "lightningcss-win32-x64-msvc": "1.31.1"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
+ "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
+ "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
+ "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
+ "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
+ "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
+ "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
+ "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
+ "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
+ "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
+ "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
+ "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
+ "license": "MIT"
+ },
+ "node_modules/log-symbols": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/log-symbols/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucia": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/lucia/-/lucia-3.2.2.tgz",
+ "integrity": "sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA==",
+ "deprecated": "This package has been deprecated. Please see https://lucia-auth.com/lucia-v3/migrate.",
+ "license": "MIT",
+ "dependencies": {
+ "@oslojs/crypto": "^1.0.1",
+ "@oslojs/encoding": "^1.1.0"
+ }
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/marky": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
+ "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
+ "license": "MIT"
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "license": "MIT"
+ },
+ "node_modules/metro": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz",
+ "integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/core": "^7.25.2",
+ "@babel/generator": "^7.25.0",
+ "@babel/parser": "^7.25.3",
+ "@babel/template": "^7.25.0",
+ "@babel/traverse": "^7.25.3",
+ "@babel/types": "^7.25.2",
+ "accepts": "^1.3.7",
+ "chalk": "^4.0.0",
+ "ci-info": "^2.0.0",
+ "connect": "^3.6.5",
+ "debug": "^4.4.0",
+ "error-stack-parser": "^2.0.6",
+ "flow-enums-runtime": "^0.0.6",
+ "graceful-fs": "^4.2.4",
+ "hermes-parser": "0.32.0",
+ "image-size": "^1.0.2",
+ "invariant": "^2.2.4",
+ "jest-worker": "^29.7.0",
+ "jsc-safe-url": "^0.2.2",
+ "lodash.throttle": "^4.1.1",
+ "metro-babel-transformer": "0.83.3",
+ "metro-cache": "0.83.3",
+ "metro-cache-key": "0.83.3",
+ "metro-config": "0.83.3",
+ "metro-core": "0.83.3",
+ "metro-file-map": "0.83.3",
+ "metro-resolver": "0.83.3",
+ "metro-runtime": "0.83.3",
+ "metro-source-map": "0.83.3",
+ "metro-symbolicate": "0.83.3",
+ "metro-transform-plugins": "0.83.3",
+ "metro-transform-worker": "0.83.3",
+ "mime-types": "^2.1.27",
+ "nullthrows": "^1.1.1",
+ "serialize-error": "^2.1.0",
+ "source-map": "^0.5.6",
+ "throat": "^5.0.0",
+ "ws": "^7.5.10",
+ "yargs": "^17.6.2"
+ },
+ "bin": {
+ "metro": "src/cli.js"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-babel-transformer": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz",
+ "integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.25.2",
+ "flow-enums-runtime": "^0.0.6",
+ "hermes-parser": "0.32.0",
+ "nullthrows": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-babel-transformer/node_modules/hermes-estree": {
+ "version": "0.32.0",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz",
+ "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==",
+ "license": "MIT"
+ },
+ "node_modules/metro-babel-transformer/node_modules/hermes-parser": {
+ "version": "0.32.0",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz",
+ "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==",
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.32.0"
+ }
+ },
+ "node_modules/metro-cache": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz",
+ "integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "exponential-backoff": "^3.1.1",
+ "flow-enums-runtime": "^0.0.6",
+ "https-proxy-agent": "^7.0.5",
+ "metro-core": "0.83.3"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-cache-key": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz",
+ "integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "flow-enums-runtime": "^0.0.6"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-config": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz",
+ "integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==",
+ "license": "MIT",
+ "dependencies": {
+ "connect": "^3.6.5",
+ "flow-enums-runtime": "^0.0.6",
+ "jest-validate": "^29.7.0",
+ "metro": "0.83.3",
+ "metro-cache": "0.83.3",
+ "metro-core": "0.83.3",
+ "metro-runtime": "0.83.3",
+ "yaml": "^2.6.1"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-core": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz",
+ "integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==",
+ "license": "MIT",
+ "dependencies": {
+ "flow-enums-runtime": "^0.0.6",
+ "lodash.throttle": "^4.1.1",
+ "metro-resolver": "0.83.3"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-file-map": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz",
+ "integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "fb-watchman": "^2.0.0",
+ "flow-enums-runtime": "^0.0.6",
+ "graceful-fs": "^4.2.4",
+ "invariant": "^2.2.4",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "nullthrows": "^1.1.1",
+ "walker": "^1.0.7"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-minify-terser": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz",
+ "integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "flow-enums-runtime": "^0.0.6",
+ "terser": "^5.15.0"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-resolver": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz",
+ "integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "flow-enums-runtime": "^0.0.6"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-runtime": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz",
+ "integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.0",
+ "flow-enums-runtime": "^0.0.6"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-source-map": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz",
+ "integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.3",
+ "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3",
+ "@babel/types": "^7.25.2",
+ "flow-enums-runtime": "^0.0.6",
+ "invariant": "^2.2.4",
+ "metro-symbolicate": "0.83.3",
+ "nullthrows": "^1.1.1",
+ "ob1": "0.83.3",
+ "source-map": "^0.5.6",
+ "vlq": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-symbolicate": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz",
+ "integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==",
+ "license": "MIT",
+ "dependencies": {
+ "flow-enums-runtime": "^0.0.6",
+ "invariant": "^2.2.4",
+ "metro-source-map": "0.83.3",
+ "nullthrows": "^1.1.1",
+ "source-map": "^0.5.6",
+ "vlq": "^1.0.0"
+ },
+ "bin": {
+ "metro-symbolicate": "src/index.js"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-transform-plugins": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz",
+ "integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.25.2",
+ "@babel/generator": "^7.25.0",
+ "@babel/template": "^7.25.0",
+ "@babel/traverse": "^7.25.3",
+ "flow-enums-runtime": "^0.0.6",
+ "nullthrows": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro-transform-worker": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz",
+ "integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.25.2",
+ "@babel/generator": "^7.25.0",
+ "@babel/parser": "^7.25.3",
+ "@babel/types": "^7.25.2",
+ "flow-enums-runtime": "^0.0.6",
+ "metro": "0.83.3",
+ "metro-babel-transformer": "0.83.3",
+ "metro-cache": "0.83.3",
+ "metro-cache-key": "0.83.3",
+ "metro-minify-terser": "0.83.3",
+ "metro-source-map": "0.83.3",
+ "metro-transform-plugins": "0.83.3",
+ "nullthrows": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/metro/node_modules/hermes-estree": {
+ "version": "0.32.0",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz",
+ "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==",
+ "license": "MIT"
+ },
+ "node_modules/metro/node_modules/hermes-parser": {
+ "version": "0.32.0",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz",
+ "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==",
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.32.0"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nested-error-stacks": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz",
+ "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==",
+ "license": "MIT"
+ },
+ "node_modules/node-abi": {
+ "version": "3.87.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
+ "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-abi/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-forge": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
+ "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
+ "license": "(BSD-3-Clause OR GPL-2.0)",
+ "engines": {
+ "node": ">= 6.13.0"
+ }
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-package-arg": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz",
+ "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==",
+ "license": "ISC",
+ "dependencies": {
+ "hosted-git-info": "^7.0.0",
+ "proc-log": "^4.0.0",
+ "semver": "^7.3.5",
+ "validate-npm-package-name": "^5.0.0"
+ },
+ "engines": {
+ "node": "^16.14.0 || >=18.0.0"
+ }
+ },
+ "node_modules/npm-package-arg/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nullthrows": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
+ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
+ "license": "MIT"
+ },
+ "node_modules/oauth4webapi": {
+ "version": "3.8.4",
+ "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.4.tgz",
+ "integrity": "sha512-EKlVEgav8zH31IXxvhCqjEgQws6S9QmnmJyLXmeV5REf59g7VmqRVa5l/rhGWtUqGm2rLVTNwukn9hla5kJ2WQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/ob1": {
+ "version": "0.83.3",
+ "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz",
+ "integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==",
+ "license": "MIT",
+ "dependencies": {
+ "flow-enums-runtime": "^0.0.6"
+ },
+ "engines": {
+ "node": ">=20.19.4"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/open": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
+ "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0",
+ "is-wsl": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
+ "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^2.4.2",
+ "cli-cursor": "^2.1.0",
+ "cli-spinners": "^2.0.0",
+ "log-symbols": "^2.2.0",
+ "strip-ansi": "^5.2.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ora/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ora/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ora/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ora/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/ora/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/ora/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/ora/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ora/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ora/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-png": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz",
+ "integrity": "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pngjs": "^3.3.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
+ "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "11.2.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz",
+ "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
+ "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/preact": {
+ "version": "10.24.3",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
+ "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "6.5.11",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
+ "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/pretty-bytes": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/proc-log": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz",
+ "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==",
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",
+ "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "asap": "~2.0.6"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qrcode-terminal": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz",
+ "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==",
+ "bin": {
+ "qrcode-terminal": "bin/qrcode-terminal.js"
+ }
+ },
+ "node_modules/query-string": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
+ "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
+ "license": "MIT",
+ "dependencies": {
+ "decode-uri-component": "^0.2.2",
+ "filter-obj": "^1.1.0",
+ "split-on-first": "^1.0.0",
+ "strict-uri-encode": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-devtools-core": {
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz",
+ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
+ "license": "MIT",
+ "dependencies": {
+ "shell-quote": "^1.6.1",
+ "ws": "^7"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-freeze": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
+ "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
+ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
+ "license": "MIT"
+ },
+ "node_modules/react-native": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz",
+ "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/create-cache-key-function": "^29.7.0",
+ "@react-native/assets-registry": "0.81.5",
+ "@react-native/codegen": "0.81.5",
+ "@react-native/community-cli-plugin": "0.81.5",
+ "@react-native/gradle-plugin": "0.81.5",
+ "@react-native/js-polyfills": "0.81.5",
+ "@react-native/normalize-colors": "0.81.5",
+ "@react-native/virtualized-lists": "0.81.5",
+ "abort-controller": "^3.0.0",
+ "anser": "^1.4.9",
+ "ansi-regex": "^5.0.0",
+ "babel-jest": "^29.7.0",
+ "babel-plugin-syntax-hermes-parser": "0.29.1",
+ "base64-js": "^1.5.1",
+ "commander": "^12.0.0",
+ "flow-enums-runtime": "^0.0.6",
+ "glob": "^7.1.1",
+ "invariant": "^2.2.4",
+ "jest-environment-node": "^29.7.0",
+ "memoize-one": "^5.0.0",
+ "metro-runtime": "^0.83.1",
+ "metro-source-map": "^0.83.1",
+ "nullthrows": "^1.1.1",
+ "pretty-format": "^29.7.0",
+ "promise": "^8.3.0",
+ "react-devtools-core": "^6.1.5",
+ "react-refresh": "^0.14.0",
+ "regenerator-runtime": "^0.13.2",
+ "scheduler": "0.26.0",
+ "semver": "^7.1.3",
+ "stacktrace-parser": "^0.1.10",
+ "whatwg-fetch": "^3.0.0",
+ "ws": "^6.2.3",
+ "yargs": "^17.6.2"
+ },
+ "bin": {
+ "react-native": "cli.js"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ },
+ "peerDependencies": {
+ "@types/react": "^19.1.0",
+ "react": "^19.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-native-fast-opencv": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/react-native-fast-opencv/-/react-native-fast-opencv-0.4.7.tgz",
+ "integrity": "sha512-0CAhHzMJni2nTPtaZundvTE06gXtElU0VRa2oUmypJPzvXOvDYQMi49P9mDtrIUQS4Jnf1OvRuGPQ1q/J8hMyQ==",
+ "license": "MIT",
+ "workspaces": [
+ "example"
+ ],
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-is-edge-to-edge": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz",
+ "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-reanimated": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz",
+ "integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "react-native-is-edge-to-edge": "^1.2.1",
+ "semver": "7.7.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0",
+ "react": "*",
+ "react-native": "*",
+ "react-native-worklets": ">=0.5.0"
+ }
+ },
+ "node_modules/react-native-reanimated/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/react-native-safe-area-context": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
+ "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-screens": {
+ "version": "4.16.0",
+ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
+ "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-freeze": "^1.0.0",
+ "react-native-is-edge-to-edge": "^1.2.1",
+ "warn-once": "^0.1.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-vision-camera": {
+ "version": "4.7.3",
+ "resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-4.7.3.tgz",
+ "integrity": "sha512-g1/neOyjSqn1kaAa2FxI/qp5KzNvPcF0bnQw6NntfbxH6tm0+8WFZszlgb5OV+iYlB6lFUztCbDtyz5IpL47OA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@shopify/react-native-skia": "*",
+ "react": "*",
+ "react-native": "*",
+ "react-native-reanimated": "*",
+ "react-native-worklets-core": "*"
+ },
+ "peerDependenciesMeta": {
+ "@shopify/react-native-skia": {
+ "optional": true
+ },
+ "react-native-reanimated": {
+ "optional": true
+ },
+ "react-native-worklets-core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-native-web": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
+ "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.6",
+ "@react-native/normalize-colors": "^0.74.1",
+ "fbjs": "^3.0.4",
+ "inline-style-prefixer": "^7.0.1",
+ "memoize-one": "^6.0.0",
+ "nullthrows": "^1.1.1",
+ "postcss-value-parser": "^4.2.0",
+ "styleq": "^0.1.3"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-native-web/node_modules/@react-native/normalize-colors": {
+ "version": "0.74.89",
+ "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz",
+ "integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==",
+ "license": "MIT"
+ },
+ "node_modules/react-native-web/node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
+ "node_modules/react-native-worklets": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz",
+ "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-arrow-functions": "^7.0.0-0",
+ "@babel/plugin-transform-class-properties": "^7.0.0-0",
+ "@babel/plugin-transform-classes": "^7.0.0-0",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
+ "@babel/plugin-transform-optional-chaining": "^7.0.0-0",
+ "@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
+ "@babel/plugin-transform-template-literals": "^7.0.0-0",
+ "@babel/plugin-transform-unicode-regex": "^7.0.0-0",
+ "@babel/preset-typescript": "^7.16.7",
+ "convert-source-map": "^2.0.0",
+ "semver": "7.7.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-worklets-core": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/react-native-worklets-core/-/react-native-worklets-core-1.6.2.tgz",
+ "integrity": "sha512-zw73JfL40ZL/OD2TOil1El4D9ZwS3l6AFPeFfUWXh+V2/dHN8i28jHX8QXlz5DYtAkR+Ju3U1h4yiaODi/igZw==",
+ "license": "MIT",
+ "dependencies": {
+ "string-hash-64": "^1.0.3"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/react-native/node_modules/@react-native/virtualized-lists": {
+ "version": "0.81.5",
+ "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
+ "integrity": "sha512-UVXgV/db25OPIvwZySeToXD/9sKKhOdkcWmmf4Jh8iBZuyfML+/5CasaZ1E7Lqg6g3uqVQq75NqIwkYmORJMPw==",
+ "license": "MIT",
+ "dependencies": {
+ "invariant": "^2.2.4",
+ "nullthrows": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 20.19.4"
+ },
+ "peerDependencies": {
+ "@types/react": "^19.1.0",
+ "react": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-native/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/react-native/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/react-native/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/react-native/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/react-native/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/react-native/node_modules/ws": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
+ "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
+ "license": "MIT",
+ "dependencies": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.31.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",
+ "integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.25.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
+ "node_modules/react-reconciler/node_modules/scheduler": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
+ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
+ "license": "MIT"
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-test-renderer": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
+ "integrity": "sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "react-is": "^19.1.0",
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+ "license": "MIT"
+ },
+ "node_modules/regenerate-unicode-properties": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
+ "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT"
+ },
+ "node_modules/regexpu-core": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
+ "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.2.2",
+ "regjsgen": "^0.8.0",
+ "regjsparser": "^0.13.0",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
+ "license": "MIT"
+ },
+ "node_modules/regjsparser": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
+ "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~3.1.0"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requireg": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz",
+ "integrity": "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==",
+ "dependencies": {
+ "nested-error-stacks": "~2.0.1",
+ "rc": "~1.2.7",
+ "resolve": "~1.7.1"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/requireg/node_modules/resolve": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
+ "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
+ "license": "MIT",
+ "dependencies": {
+ "path-parse": "^1.0.5"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-global": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz",
+ "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==",
+ "license": "MIT",
+ "dependencies": {
+ "global-dirs": "^0.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/resolve-workspace-root": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz",
+ "integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==",
+ "license": "MIT"
+ },
+ "node_modules/resolve.exports": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/sax": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serialize-error": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
+ "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/serve-static/node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/server-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
+ "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
+ "license": "MIT"
+ },
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "license": "MIT"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/sf-symbols-typescript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz",
+ "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/simple-plist": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
+ "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==",
+ "license": "MIT",
+ "dependencies": {
+ "bplist-creator": "0.1.0",
+ "bplist-parser": "0.3.1",
+ "plist": "^3.0.5"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "license": "MIT"
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slugify": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
+ "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/split-on-first": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stackframe": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
+ "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
+ "license": "MIT"
+ },
+ "node_modules/stacktrace-parser": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz",
+ "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==",
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.7.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/stream-buffers": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
+ "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/strict-uri-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-hash-64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string-hash-64/-/string-hash-64-1.0.3.tgz",
+ "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==",
+ "license": "MIT"
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/structured-headers": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz",
+ "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==",
+ "license": "MIT"
+ },
+ "node_modules/styleq": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz",
+ "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==",
+ "license": "MIT"
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "license": "MIT",
+ "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"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-hyperlinks": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
+ "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.5.7",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
+ "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-fs/node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/temp-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
+ "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/terminal-link": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
+ "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^4.2.1",
+ "supports-hyperlinks": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.46.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
+ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/test-exclude/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/throat": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
+ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
+ "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ua-parser-js": {
+ "version": "1.0.41",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
+ "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "ua-parser-js": "script/cli.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/undici": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
+ "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-value-ecmascript": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
+ "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-property-aliases-ecmascript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unique-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
+ "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
+ "license": "MIT",
+ "dependencies": {
+ "crypto-random-string": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-latest-callback": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
+ "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
+ "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/validate-npm-package-name": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
+ "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==",
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vaul": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
+ "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vaul/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vlq": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
+ "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==",
+ "license": "MIT"
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/warn-once": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
+ "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
+ "license": "MIT"
+ },
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-fetch": {
+ "version": "3.6.20",
+ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
+ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
+ "license": "MIT"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/whatwg-url-without-unicode": {
+ "version": "8.0.0-3",
+ "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
+ "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.4.3",
+ "punycode": "^2.1.1",
+ "webidl-conversions": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
+ "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wonka": {
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz",
+ "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xcode": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz",
+ "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "simple-plist": "^1.1.0",
+ "uuid": "^7.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/xml2js": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
+ "integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xml2js/node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+ "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2e2113b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "app",
+ "main": "expo-router/entry",
+ "version": "1.0.0",
+ "scripts": {
+ "start": "expo start",
+ "dev": "concurrently \"bunx convex dev\" \"expo start --android\"",
+ "dev:convex": "bunx convex dev",
+ "dev:expo": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "migrate": "tsx scripts/migrate-hashes.ts",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@auth/core": "^0.37.4",
+ "@convex-dev/auth": "^0.0.90",
+ "@expo/vector-icons": "^15.0.3",
+ "@react-navigation/native": "^7.1.8",
+ "@shopify/react-native-skia": "^2.4.18",
+ "convex": "^1.31.7",
+ "expo": "~54.0.33",
+ "expo-auth-session": "^7.0.10",
+ "expo-camera": "^17.0.10",
+ "expo-constants": "~18.0.13",
+ "expo-crypto": "^15.0.8",
+ "expo-font": "~14.0.11",
+ "expo-image-manipulator": "^14.0.8",
+ "expo-linking": "~8.0.11",
+ "expo-router": "~6.0.23",
+ "expo-secure-store": "^15.0.8",
+ "expo-splash-screen": "~31.0.13",
+ "expo-sqlite": "^16.0.10",
+ "expo-status-bar": "~3.0.9",
+ "expo-web-browser": "~15.0.10",
+ "react": "19.1.0",
+ "react-dom": "19.1.0",
+ "react-native": "0.81.5",
+ "react-native-fast-opencv": "^0.4.7",
+ "react-native-reanimated": "~4.1.1",
+ "react-native-safe-area-context": "~5.6.0",
+ "react-native-screens": "~4.16.0",
+ "react-native-vision-camera": "^4.7.3",
+ "react-native-web": "~0.21.0",
+ "react-native-worklets": "0.5.1",
+ "react-native-worklets-core": "^1.6.2"
+ },
+ "devDependencies": {
+ "@types/better-sqlite3": "^7.6.12",
+ "@types/node": "^22.10.0",
+ "@types/react": "~19.1.0",
+ "better-sqlite3": "^11.6.0",
+ "concurrently": "^9.1.0",
+ "dotenv": "^16.4.7",
+ "react-test-renderer": "19.1.0",
+ "tsx": "^4.19.2",
+ "typescript": "~5.9.2"
+ },
+ "private": true
+}
diff --git a/scripts/migrate-hashes.ts b/scripts/migrate-hashes.ts
new file mode 100644
index 0000000..7dcc42a
--- /dev/null
+++ b/scripts/migrate-hashes.ts
@@ -0,0 +1,116 @@
+/**
+ * Migration script to upload card hashes from SQLite to Convex.
+ *
+ * Usage:
+ * npx tsx scripts/migrate-hashes.ts
+ *
+ * Prerequisites:
+ * - Run `npx convex dev` first to set up the Convex project
+ * - Ensure CONVEX_URL is set in .env.local
+ */
+
+import { ConvexHttpClient } from "convex/browser";
+import Database from "better-sqlite3";
+import { api } from "../convex/_generated/api";
+import * as dotenv from "dotenv";
+import * as path from "path";
+import * as fs from "fs";
+
+// Load environment variables
+dotenv.config({ path: path.join(__dirname, "..", ".env.local") });
+
+const CONVEX_URL = process.env.EXPO_PUBLIC_CONVEX_URL || process.env.CONVEX_URL;
+const DB_PATH =
+ process.env.DB_PATH || path.join(__dirname, "..", "card_hashes.db");
+const BATCH_SIZE = 50;
+const HASH_VERSION = 1;
+
+interface CardRow {
+ id: string;
+ oracle_id: string;
+ name: string;
+ set_code: string;
+ collector_number: string | null;
+ rarity: string | null;
+ artist: string | null;
+ image_uri: string | null;
+ hash: Buffer;
+}
+
+async function main() {
+ if (!CONVEX_URL) {
+ console.error("Error: CONVEX_URL not set in .env.local");
+ console.error("Run 'npx convex dev --once --configure=new' first");
+ process.exit(1);
+ }
+
+ if (!fs.existsSync(DB_PATH)) {
+ console.error(`Error: Database not found at ${DB_PATH}`);
+ process.exit(1);
+ }
+
+ console.log(`Connecting to Convex: ${CONVEX_URL}`);
+ console.log(`Reading from SQLite: ${DB_PATH}`);
+
+ const client = new ConvexHttpClient(CONVEX_URL);
+ const db = new Database(DB_PATH, { readonly: true });
+
+ try {
+ // Get total count
+ const countRow = db
+ .prepare("SELECT COUNT(*) as count FROM cards WHERE hash IS NOT NULL")
+ .get() as { count: number };
+ const totalCards = countRow.count;
+ console.log(`Found ${totalCards} cards with hashes to migrate`);
+
+ // Query cards with hashes
+ const stmt = db.prepare(`
+ SELECT id, oracle_id, name, set_code, collector_number, rarity, artist, image_uri, hash
+ FROM cards
+ WHERE hash IS NOT NULL
+ ORDER BY name
+ `);
+
+ const cards = stmt.all() as CardRow[];
+ let migrated = 0;
+ let errors = 0;
+
+ // Process in batches
+ for (let i = 0; i < cards.length; i += BATCH_SIZE) {
+ const batch = cards.slice(i, i + BATCH_SIZE);
+ const convexCards = batch.map((card) => ({
+ scryfallId: card.id,
+ oracleId: card.oracle_id,
+ name: card.name,
+ setCode: card.set_code,
+ collectorNumber: card.collector_number || "",
+ rarity: card.rarity || "common",
+ artist: card.artist || undefined,
+ imageUri: card.image_uri || undefined,
+ hash: new Uint8Array(card.hash).buffer as ArrayBuffer,
+ hashVersion: HASH_VERSION,
+ }));
+
+ try {
+ await client.mutation(api.cards.insertBatch, { cards: convexCards });
+ migrated += batch.length;
+ const pct = ((migrated / totalCards) * 100).toFixed(1);
+ process.stdout.write(`\rMigrated ${migrated}/${totalCards} (${pct}%)`);
+ } catch (err) {
+ console.error(`\nError migrating batch starting at ${i}:`, err);
+ errors += batch.length;
+ }
+ }
+
+ console.log(`\n\nMigration complete!`);
+ console.log(` Migrated: ${migrated}`);
+ console.log(` Errors: ${errors}`);
+ } finally {
+ db.close();
+ }
+}
+
+main().catch((err) => {
+ console.error("Migration failed:", err);
+ process.exit(1);
+});
diff --git a/src/Scry.App/App.xaml b/src/Scry.App/App.xaml
deleted file mode 100644
index 311b5d9..0000000
--- a/src/Scry.App/App.xaml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/App.xaml.cs b/src/Scry.App/App.xaml.cs
deleted file mode 100644
index 5f51db0..0000000
--- a/src/Scry.App/App.xaml.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Scry;
-
-public partial class App : Application
-{
- public App()
- {
- InitializeComponent();
- }
-
- protected override Window CreateWindow(IActivationState? activationState)
- {
- return new Window(new AppShell());
- }
-}
diff --git a/src/Scry.App/AppShell.xaml b/src/Scry.App/AppShell.xaml
deleted file mode 100644
index f516875..0000000
--- a/src/Scry.App/AppShell.xaml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/AppShell.xaml.cs b/src/Scry.App/AppShell.xaml.cs
deleted file mode 100644
index c0d57ff..0000000
--- a/src/Scry.App/AppShell.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Scry.Views;
-
-namespace Scry;
-
-public partial class AppShell : Shell
-{
- public AppShell()
- {
- InitializeComponent();
-
- Routing.RegisterRoute(nameof(CardDetailPage), typeof(CardDetailPage));
- }
-}
diff --git a/src/Scry.App/Converters/BoolToScanTextConverter.cs b/src/Scry.App/Converters/BoolToScanTextConverter.cs
deleted file mode 100644
index de0eec1..0000000
--- a/src/Scry.App/Converters/BoolToScanTextConverter.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Globalization;
-
-namespace Scry.Converters;
-
-public class BoolToScanTextConverter : IValueConverter
-{
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if (value is bool isProcessing)
- return isProcessing ? "Scanning..." : "Scan Card";
- return "Scan Card";
- }
-
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
-}
diff --git a/src/Scry.App/Converters/InverseBoolConverter.cs b/src/Scry.App/Converters/InverseBoolConverter.cs
deleted file mode 100644
index 9d32a3a..0000000
--- a/src/Scry.App/Converters/InverseBoolConverter.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Globalization;
-
-namespace Scry.Converters;
-
-public class InverseBoolConverter : IValueConverter
-{
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if (value is bool b)
- return !b;
- return value;
- }
-
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if (value is bool b)
- return !b;
- return value;
- }
-}
diff --git a/src/Scry.App/Converters/StringNotEmptyConverter.cs b/src/Scry.App/Converters/StringNotEmptyConverter.cs
deleted file mode 100644
index d549f8f..0000000
--- a/src/Scry.App/Converters/StringNotEmptyConverter.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Globalization;
-
-namespace Scry.Converters;
-
-public class StringNotEmptyConverter : IValueConverter
-{
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- return !string.IsNullOrWhiteSpace(value as string);
- }
-
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
-}
diff --git a/src/Scry.App/MauiProgram.cs b/src/Scry.App/MauiProgram.cs
deleted file mode 100644
index ebeca2a..0000000
--- a/src/Scry.App/MauiProgram.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using CommunityToolkit.Maui;
-using Microsoft.Extensions.Logging;
-using Scry.Core.Data;
-using Scry.Core.Recognition;
-using Scry.Services;
-using Scry.ViewModels;
-using Scry.Views;
-
-namespace Scry;
-
-public static class MauiProgram
-{
- public static MauiApp CreateMauiApp()
- {
- var builder = MauiApp.CreateBuilder();
- builder
- .UseMauiApp()
- .UseMauiCommunityToolkit()
- .UseMauiCommunityToolkitCamera()
- .ConfigureFonts(fonts =>
- {
- fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
- fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
- });
-
- // Core Services (from Scry.Core)
- builder.Services.AddSingleton(sp =>
- {
- var dbPath = Path.Combine(FileSystem.AppDataDirectory, "card_hashes.db");
- EnsureDatabaseCopied(dbPath);
- return new CardDatabase(dbPath);
- });
-
- // Recognition options - configure debug output in DEBUG builds
- builder.Services.Configure(options =>
- {
-#if DEBUG && ANDROID
- // Use Download folder for easy adb pull access
- options.DebugOutputDirectory = "/sdcard/Download/scry-debug";
-#elif DEBUG
- options.DebugOutputDirectory = "./debug";
-#endif
- });
- builder.Services.AddSingleton();
-
- // App Services
- builder.Services.AddSingleton();
- builder.Services.AddSingleton();
-
- // ViewModels
- builder.Services.AddTransient();
- builder.Services.AddTransient();
- builder.Services.AddTransient();
- builder.Services.AddTransient();
-
- // Views
- builder.Services.AddTransient();
- builder.Services.AddTransient();
- builder.Services.AddTransient();
- builder.Services.AddTransient();
-
-#if DEBUG
- builder.Logging.AddDebug();
-#endif
-
- return builder.Build();
- }
-
- private static void EnsureDatabaseCopied(string targetPath)
- {
- try
- {
- using var bundledStream = FileSystem.OpenAppPackageFileAsync("card_hashes.db").GetAwaiter().GetResult();
-
- if (File.Exists(targetPath))
- {
- // Compare sizes - if bundled is larger, replace
- var existingSize = new FileInfo(targetPath).Length;
- var bundledSize = bundledStream.Length;
-
- if (bundledSize <= existingSize)
- return;
-
- // Bundled db is larger, delete old and copy new
- File.Delete(targetPath);
- }
-
- using var fileStream = File.Create(targetPath);
- bundledStream.CopyTo(fileStream);
- }
- catch
- {
- // Database not bundled, will be empty
- }
- }
-}
diff --git a/src/Scry.App/Models/CollectionEntry.cs b/src/Scry.App/Models/CollectionEntry.cs
deleted file mode 100644
index 4b523d2..0000000
--- a/src/Scry.App/Models/CollectionEntry.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Scry.Core.Models;
-
-namespace Scry.Models;
-
-public class CollectionEntry
-{
- public string Id { get; set; } = Guid.NewGuid().ToString();
- public Card Card { get; set; } = null!;
- public int Quantity { get; set; } = 1;
- public bool IsFoil { get; set; }
- public DateTime AddedAt { get; set; } = DateTime.UtcNow;
-}
diff --git a/src/Scry.App/Platforms/Android/AndroidManifest.xml b/src/Scry.App/Platforms/Android/AndroidManifest.xml
deleted file mode 100644
index a3ec4f5..0000000
--- a/src/Scry.App/Platforms/Android/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Platforms/Android/MainActivity.cs b/src/Scry.App/Platforms/Android/MainActivity.cs
deleted file mode 100644
index e569cf3..0000000
--- a/src/Scry.App/Platforms/Android/MainActivity.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Android.App;
-using Android.Content.PM;
-using Android.OS;
-
-namespace Scry;
-
-[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
- ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode |
- ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
-public class MainActivity : MauiAppCompatActivity
-{
-}
diff --git a/src/Scry.App/Platforms/Android/MainApplication.cs b/src/Scry.App/Platforms/Android/MainApplication.cs
deleted file mode 100644
index 0e985c5..0000000
--- a/src/Scry.App/Platforms/Android/MainApplication.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Android.App;
-using Android.Runtime;
-
-namespace Scry;
-
-[Application]
-public class MainApplication : MauiApplication
-{
- public MainApplication(IntPtr handle, JniHandleOwnership ownership)
- : base(handle, ownership)
- {
- }
-
- protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
-}
diff --git a/src/Scry.App/Platforms/Android/Resources/values/colors.xml b/src/Scry.App/Platforms/Android/Resources/values/colors.xml
deleted file mode 100644
index 614ac41..0000000
--- a/src/Scry.App/Platforms/Android/Resources/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- #512BD4
- #3B1F9E
- #512BD4
-
diff --git a/src/Scry.App/Resources/AppIcon/appicon.svg b/src/Scry.App/Resources/AppIcon/appicon.svg
deleted file mode 100644
index 86e49b4..0000000
--- a/src/Scry.App/Resources/AppIcon/appicon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/src/Scry.App/Resources/AppIcon/appiconfg.svg b/src/Scry.App/Resources/AppIcon/appiconfg.svg
deleted file mode 100644
index 76d01d6..0000000
--- a/src/Scry.App/Resources/AppIcon/appiconfg.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
diff --git a/src/Scry.App/Resources/Fonts/.gitkeep b/src/Scry.App/Resources/Fonts/.gitkeep
deleted file mode 100644
index dcf2c80..0000000
--- a/src/Scry.App/Resources/Fonts/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-# Placeholder
diff --git a/src/Scry.App/Resources/Raw/.gitkeep b/src/Scry.App/Resources/Raw/.gitkeep
deleted file mode 100644
index dcf2c80..0000000
--- a/src/Scry.App/Resources/Raw/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-# Placeholder
diff --git a/src/Scry.App/Resources/Splash/splash.svg b/src/Scry.App/Resources/Splash/splash.svg
deleted file mode 100644
index be886b2..0000000
--- a/src/Scry.App/Resources/Splash/splash.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
diff --git a/src/Scry.App/Resources/Styles/Colors.xaml b/src/Scry.App/Resources/Styles/Colors.xaml
deleted file mode 100644
index 9f295a7..0000000
--- a/src/Scry.App/Resources/Styles/Colors.xaml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
- #512BD4
- #3B1F9E
- White
- #DFD8F7
- #3B1F9E
- #2B0B98
-
- White
- Black
- #E1E1E1
- #C8C8C8
- #ACACAC
- #919191
- #6E6E6E
- #404040
- #2A2A2A
- #1A1A1A
- #141414
-
- #F7B548
- #FFD590
- #FFE5B9
- #28C2D1
- #7BDDEF
- #C3F2F4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Resources/Styles/Styles.xaml b/src/Scry.App/Resources/Styles/Styles.xaml
deleted file mode 100644
index a1c5607..0000000
--- a/src/Scry.App/Resources/Styles/Styles.xaml
+++ /dev/null
@@ -1,184 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Scry.App.csproj b/src/Scry.App/Scry.App.csproj
deleted file mode 100644
index 3a04d80..0000000
--- a/src/Scry.App/Scry.App.csproj
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
-
- $(LOCALAPPDATA)\Android\Sdk
- C:\Program Files\Microsoft\jdk-21.0.10.7-hotspot
-
-
-
- net10.0-android
- android-arm64;android-x64
- true
-
- Exe
- Scry
- true
- true
- enable
- enable
-
-
-
-
- true
- true
- true
-
-
-
-
-
-
-
-
-
- Scry
-
-
- land.charm.scry
-
-
- 1.0
- 1
-
- 21.0
- 15.0
- 15.0
- 10.0.17763.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Services/ICardRecognitionService.cs b/src/Scry.App/Services/ICardRecognitionService.cs
deleted file mode 100644
index c481e11..0000000
--- a/src/Scry.App/Services/ICardRecognitionService.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Scry.Core.Models;
-
-namespace Scry.Services;
-
-public interface ICardRecognitionService
-{
- Task RecognizeCardAsync(Stream imageStream, CancellationToken cancellationToken = default);
-}
diff --git a/src/Scry.App/Services/ICardRepository.cs b/src/Scry.App/Services/ICardRepository.cs
deleted file mode 100644
index 2eaa3fa..0000000
--- a/src/Scry.App/Services/ICardRepository.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Scry.Core.Models;
-using Scry.Models;
-
-namespace Scry.Services;
-
-public interface ICardRepository
-{
- IReadOnlyList GetAll();
- CollectionEntry? GetById(string id);
- void Add(Card card, int quantity = 1, bool isFoil = false);
- void UpdateQuantity(string entryId, int newQuantity);
- void Remove(string entryId);
- int TotalCards { get; }
- int UniqueCards { get; }
-}
diff --git a/src/Scry.App/Services/InMemoryCardRepository.cs b/src/Scry.App/Services/InMemoryCardRepository.cs
deleted file mode 100644
index e56f8d2..0000000
--- a/src/Scry.App/Services/InMemoryCardRepository.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using Scry.Core.Models;
-using Scry.Models;
-
-namespace Scry.Services;
-
-public class InMemoryCardRepository : ICardRepository
-{
- private readonly List _entries = [];
- private readonly object _lock = new();
-
- public IReadOnlyList GetAll()
- {
- lock (_lock)
- {
- return _entries.ToList();
- }
- }
-
- public CollectionEntry? GetById(string id)
- {
- lock (_lock)
- {
- return _entries.FirstOrDefault(e => e.Id == id);
- }
- }
-
- public void Add(Card card, int quantity = 1, bool isFoil = false)
- {
- lock (_lock)
- {
- // Check if we already have this exact card (same id + foil status)
- var existing = _entries.FirstOrDefault(e =>
- e.Card.Id == card.Id && e.IsFoil == isFoil);
-
- if (existing != null)
- {
- existing.Quantity += quantity;
- }
- else
- {
- _entries.Add(new CollectionEntry
- {
- Card = card,
- Quantity = quantity,
- IsFoil = isFoil
- });
- }
- }
- }
-
- public void UpdateQuantity(string entryId, int newQuantity)
- {
- lock (_lock)
- {
- var entry = _entries.FirstOrDefault(e => e.Id == entryId);
- if (entry != null)
- {
- if (newQuantity <= 0)
- {
- _entries.Remove(entry);
- }
- else
- {
- entry.Quantity = newQuantity;
- }
- }
- }
- }
-
- public void Remove(string entryId)
- {
- lock (_lock)
- {
- var entry = _entries.FirstOrDefault(e => e.Id == entryId);
- if (entry != null)
- {
- _entries.Remove(entry);
- }
- }
- }
-
- public int TotalCards
- {
- get
- {
- lock (_lock)
- {
- return _entries.Sum(e => e.Quantity);
- }
- }
- }
-
- public int UniqueCards
- {
- get
- {
- lock (_lock)
- {
- return _entries.Count;
- }
- }
- }
-}
diff --git a/src/Scry.App/Services/MockCardRecognitionService.cs b/src/Scry.App/Services/MockCardRecognitionService.cs
deleted file mode 100644
index a2db310..0000000
--- a/src/Scry.App/Services/MockCardRecognitionService.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-using Scry.Core.Models;
-
-namespace Scry.Services;
-
-///
-/// Mock implementation that returns random MTG cards for testing.
-/// Replace with RealCardRecognitionService for production use.
-///
-public class MockCardRecognitionService : ICardRecognitionService
-{
- private static readonly Card[] SampleCards =
- [
- new Card
- {
- Id = "4cbc6901-6a4a-4d0a-83ea-7eefa3b35021",
- OracleId = "orb-sol-ring",
- SetId = "set-c21",
- Name = "Sol Ring",
- SetCode = "C21",
- SetName = "Commander 2021",
- CollectorNumber = "263",
- ImageUri = "https://cards.scryfall.io/normal/front/4/c/4cbc6901-6a4a-4d0a-83ea-7eefa3b35021.jpg",
- ManaCost = "{1}",
- TypeLine = "Artifact",
- OracleText = "{T}: Add {C}{C}.",
- Rarity = "uncommon",
- PricesUsd = 1.50m
- },
- new Card
- {
- Id = "e3285e6b-3e79-4d7c-bf96-d920f973b122",
- OracleId = "orb-lightning-bolt",
- SetId = "set-2xm",
- Name = "Lightning Bolt",
- SetCode = "2XM",
- SetName = "Double Masters",
- CollectorNumber = "129",
- ImageUri = "https://cards.scryfall.io/normal/front/e/3/e3285e6b-3e79-4d7c-bf96-d920f973b122.jpg",
- ManaCost = "{R}",
- TypeLine = "Instant",
- OracleText = "Lightning Bolt deals 3 damage to any target.",
- Rarity = "uncommon",
- PricesUsd = 2.00m
- },
- new Card
- {
- Id = "ce30f926-bc06-46ee-9f35-0c32659a1b1c",
- OracleId = "orb-counterspell",
- SetId = "set-cmr",
- Name = "Counterspell",
- SetCode = "CMR",
- SetName = "Commander Legends",
- CollectorNumber = "395",
- ImageUri = "https://cards.scryfall.io/normal/front/c/e/ce30f926-bc06-46ee-9f35-0c32659a1b1c.jpg",
- ManaCost = "{U}{U}",
- TypeLine = "Instant",
- OracleText = "Counter target spell.",
- Rarity = "uncommon",
- PricesUsd = 1.25m
- },
- new Card
- {
- Id = "73542c66-eb3a-46e8-a8f6-5f02087b28cf",
- OracleId = "orb-llanowar-elves",
- SetId = "set-m19",
- Name = "Llanowar Elves",
- SetCode = "M19",
- SetName = "Core Set 2019",
- CollectorNumber = "314",
- ImageUri = "https://cards.scryfall.io/normal/front/7/3/73542c66-eb3a-46e8-a8f6-5f02087b28cf.jpg",
- ManaCost = "{G}",
- TypeLine = "Creature — Elf Druid",
- OracleText = "{T}: Add {G}.",
- Rarity = "common",
- PricesUsd = 0.25m
- },
- new Card
- {
- Id = "b8dcd4a6-5c0b-4e1e-8e1d-2e2e5c1d6a1e",
- OracleId = "orb-swords-to-plowshares",
- SetId = "set-cmr",
- Name = "Swords to Plowshares",
- SetCode = "CMR",
- SetName = "Commander Legends",
- CollectorNumber = "387",
- ImageUri = "https://cards.scryfall.io/normal/front/b/8/b8dcd4a6-5c0b-4e1e-8e1d-2e2e5c1d6a1e.jpg",
- ManaCost = "{W}",
- TypeLine = "Instant",
- OracleText = "Exile target creature. Its controller gains life equal to its power.",
- Rarity = "uncommon",
- PricesUsd = 3.50m
- },
- new Card
- {
- Id = "bd8fa327-dd41-4737-8f19-2cf5eb1f7c2e",
- OracleId = "orb-black-lotus",
- SetId = "set-lea",
- Name = "Black Lotus",
- SetCode = "LEA",
- SetName = "Limited Edition Alpha",
- CollectorNumber = "232",
- ImageUri = "https://cards.scryfall.io/normal/front/b/d/bd8fa327-dd41-4737-8f19-2cf5eb1f7c2e.jpg",
- ManaCost = "{0}",
- TypeLine = "Artifact",
- OracleText = "{T}, Sacrifice Black Lotus: Add three mana of any one color.",
- Rarity = "rare",
- PricesUsd = 500000.00m
- }
- ];
-
- private readonly Random _random = new();
-
- public async Task RecognizeCardAsync(Stream imageStream, CancellationToken cancellationToken = default)
- {
- // Simulate processing delay
- await Task.Delay(500 + _random.Next(500), cancellationToken);
-
- // 90% success rate
- if (_random.NextDouble() < 0.1)
- {
- return ScanResult.Failed("Could not recognize card. Please try again with better lighting.");
- }
-
- var card = SampleCards[_random.Next(SampleCards.Length)];
- var confidence = 0.75f + (float)_random.NextDouble() * 0.24f; // 75-99% confidence
-
- return ScanResult.Matched(card, confidence, 10, TimeSpan.FromMilliseconds(500));
- }
-}
diff --git a/src/Scry.App/Services/RealCardRecognitionService.cs b/src/Scry.App/Services/RealCardRecognitionService.cs
deleted file mode 100644
index 1afb57f..0000000
--- a/src/Scry.App/Services/RealCardRecognitionService.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Scry.Core.Models;
-using Scry.Core.Recognition;
-
-namespace Scry.Services;
-
-///
-/// Real implementation that uses Scry.Core's perceptual hash-based card recognition.
-///
-public class RealCardRecognitionService : ICardRecognitionService
-{
- private readonly CardRecognitionService _recognitionService;
-
- public RealCardRecognitionService(CardRecognitionService recognitionService)
- {
- _recognitionService = recognitionService;
- }
-
- public async Task RecognizeCardAsync(Stream imageStream, CancellationToken cancellationToken = default)
- {
- return await _recognitionService.RecognizeAsync(imageStream, cancellationToken);
- }
-}
diff --git a/src/Scry.App/ViewModels/CardDetailViewModel.cs b/src/Scry.App/ViewModels/CardDetailViewModel.cs
deleted file mode 100644
index f50c2ff..0000000
--- a/src/Scry.App/ViewModels/CardDetailViewModel.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using Scry.Core.Models;
-using Scry.Models;
-using Scry.Services;
-
-namespace Scry.ViewModels;
-
-[QueryProperty(nameof(CardId), "cardId")]
-[QueryProperty(nameof(EntryId), "entryId")]
-public partial class CardDetailViewModel : ObservableObject
-{
- private readonly ICardRepository _cardRepository;
-
- [ObservableProperty]
- private Card? _card;
-
- [ObservableProperty]
- private CollectionEntry? _entry;
-
- [ObservableProperty]
- private bool _isInCollection;
-
- [ObservableProperty]
- private string? _cardId;
-
- [ObservableProperty]
- private string? _entryId;
-
- public CardDetailViewModel(ICardRepository cardRepository)
- {
- _cardRepository = cardRepository;
- }
-
- partial void OnEntryIdChanged(string? value)
- {
- if (string.IsNullOrEmpty(value)) return;
-
- Entry = _cardRepository.GetById(value);
- if (Entry != null)
- {
- Card = Entry.Card;
- IsInCollection = true;
- }
- }
-
- partial void OnCardIdChanged(string? value)
- {
- if (string.IsNullOrEmpty(value)) return;
-
- // For now, find by looking through collection
- var entries = _cardRepository.GetAll();
- Entry = entries.FirstOrDefault(e => e.Card.Id == value);
- if (Entry != null)
- {
- Card = Entry.Card;
- IsInCollection = true;
- }
- }
-
- [RelayCommand]
- private void IncrementQuantity()
- {
- if (Entry == null) return;
- _cardRepository.UpdateQuantity(Entry.Id, Entry.Quantity + 1);
- Entry = _cardRepository.GetById(Entry.Id);
- }
-
- [RelayCommand]
- private void DecrementQuantity()
- {
- if (Entry == null) return;
-
- if (Entry.Quantity > 1)
- {
- _cardRepository.UpdateQuantity(Entry.Id, Entry.Quantity - 1);
- Entry = _cardRepository.GetById(Entry.Id);
- }
- }
-
- [RelayCommand]
- private async Task GoBackAsync()
- {
- await Shell.Current.GoToAsync("..");
- }
-}
diff --git a/src/Scry.App/ViewModels/CollectionViewModel.cs b/src/Scry.App/ViewModels/CollectionViewModel.cs
deleted file mode 100644
index 677e921..0000000
--- a/src/Scry.App/ViewModels/CollectionViewModel.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using Scry.Core.Models;
-using Scry.Models;
-using Scry.Services;
-using Scry.Views;
-using System.Collections.ObjectModel;
-
-namespace Scry.ViewModels;
-
-public partial class CollectionViewModel : ObservableObject
-{
- private readonly ICardRepository _cardRepository;
-
- [ObservableProperty]
- private ObservableCollection _entries = [];
-
- [ObservableProperty]
- private int _totalCards;
-
- [ObservableProperty]
- private int _uniqueCards;
-
- [ObservableProperty]
- private bool _isEmpty = true;
-
- public CollectionViewModel(ICardRepository cardRepository)
- {
- _cardRepository = cardRepository;
- }
-
- [RelayCommand]
- private void LoadCollection()
- {
- var allEntries = _cardRepository.GetAll();
- Entries = new ObservableCollection(allEntries.OrderByDescending(e => e.AddedAt));
- TotalCards = _cardRepository.TotalCards;
- UniqueCards = _cardRepository.UniqueCards;
- IsEmpty = Entries.Count == 0;
- }
-
- [RelayCommand]
- private async Task ViewCardAsync(CollectionEntry entry)
- {
- await Shell.Current.GoToAsync($"{nameof(CardDetailPage)}?entryId={entry.Id}");
- }
-
- [RelayCommand]
- private void IncrementQuantity(CollectionEntry entry)
- {
- _cardRepository.UpdateQuantity(entry.Id, entry.Quantity + 1);
- LoadCollection();
- }
-
- [RelayCommand]
- private void DecrementQuantity(CollectionEntry entry)
- {
- if (entry.Quantity > 1)
- {
- _cardRepository.UpdateQuantity(entry.Id, entry.Quantity - 1);
- }
- else
- {
- _cardRepository.Remove(entry.Id);
- }
- LoadCollection();
- }
-
- [RelayCommand]
- private async Task RemoveEntryAsync(CollectionEntry entry)
- {
- var result = await Shell.Current.DisplayAlertAsync(
- "Remove Card",
- $"Remove all {entry.Quantity}x {entry.Card.Name} from collection?",
- "Remove",
- "Cancel");
-
- if (result)
- {
- _cardRepository.Remove(entry.Id);
- LoadCollection();
- }
- }
-}
diff --git a/src/Scry.App/ViewModels/ScanViewModel.cs b/src/Scry.App/ViewModels/ScanViewModel.cs
deleted file mode 100644
index 8d1951a..0000000
--- a/src/Scry.App/ViewModels/ScanViewModel.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using CommunityToolkit.Maui.Views;
-using Scry.Core.Models;
-using Scry.Services;
-using Scry.Views;
-
-namespace Scry.ViewModels;
-
-public partial class ScanViewModel : ObservableObject
-{
- private readonly ICardRecognitionService _recognitionService;
- private readonly ICardRepository _cardRepository;
-
- [ObservableProperty]
- private bool _isScanning;
-
- [ObservableProperty]
- private bool _isProcessing;
-
- [ObservableProperty]
- private string? _statusMessage;
-
- [ObservableProperty]
- private Card? _lastScannedCard;
-
- [ObservableProperty]
- private float _lastConfidence;
-
- [ObservableProperty]
- private bool _hasResult;
-
- [ObservableProperty]
- private bool _isFoil;
-
- public ScanViewModel(
- ICardRecognitionService recognitionService,
- ICardRepository cardRepository)
- {
- _recognitionService = recognitionService;
- _cardRepository = cardRepository;
- StatusMessage = "Point camera at a card and tap Scan";
- }
-
- [RelayCommand]
- private async Task CaptureAndRecognizeAsync(CameraView cameraView)
- {
- if (IsProcessing) return;
-
- try
- {
- IsProcessing = true;
- StatusMessage = "Capturing...";
-
- // Capture image from camera
- using var imageStream = await cameraView.CaptureImage(CancellationToken.None);
- if (imageStream == null)
- {
- StatusMessage = "Failed to capture image";
- return;
- }
-
- StatusMessage = "Recognizing card...";
-
- // Copy to memory stream since the camera stream might not be seekable
- using var memoryStream = new MemoryStream();
- await imageStream.CopyToAsync(memoryStream);
- memoryStream.Position = 0;
-
- var result = await _recognitionService.RecognizeCardAsync(memoryStream);
-
- if (result.Success && result.Card != null)
- {
- LastScannedCard = result.Card;
- LastConfidence = result.Confidence;
- HasResult = true;
- StatusMessage = $"Found: {result.Card.Name} ({result.Confidence:P0})";
- }
- else
- {
- HasResult = false;
- StatusMessage = result.ErrorMessage ?? "Recognition failed";
- }
- }
- catch (Exception ex)
- {
- StatusMessage = $"Error: {ex.Message}";
- HasResult = false;
- }
- finally
- {
- IsProcessing = false;
- }
- }
-
- [RelayCommand]
- private async Task AddToCollectionAsync()
- {
- if (LastScannedCard == null) return;
-
- _cardRepository.Add(LastScannedCard, 1, IsFoil);
-
- var foilText = IsFoil ? " (Foil)" : "";
- StatusMessage = $"Added {LastScannedCard.Name}{foilText} to collection!";
-
- // Reset for next scan
- await Task.Delay(1500);
- HasResult = false;
- LastScannedCard = null;
- IsFoil = false;
- StatusMessage = "Point camera at a card and tap Scan";
- }
-
- [RelayCommand]
- private void CancelResult()
- {
- HasResult = false;
- LastScannedCard = null;
- IsFoil = false;
- StatusMessage = "Point camera at a card and tap Scan";
- }
-
- [RelayCommand]
- private async Task ViewCardDetailsAsync()
- {
- if (LastScannedCard == null) return;
-
- await Shell.Current.GoToAsync($"{nameof(CardDetailPage)}?cardId={LastScannedCard.Id}");
- }
-}
diff --git a/src/Scry.App/ViewModels/SettingsViewModel.cs b/src/Scry.App/ViewModels/SettingsViewModel.cs
deleted file mode 100644
index 407a913..0000000
--- a/src/Scry.App/ViewModels/SettingsViewModel.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using Scry.Core.Data;
-
-namespace Scry.ViewModels;
-
-public partial class SettingsViewModel : ObservableObject
-{
- private readonly CardDatabase _database;
-
- [ObservableProperty]
- private int _cardCount;
-
- [ObservableProperty]
- private int _oracleCount;
-
- [ObservableProperty]
- private int _setCount;
-
- [ObservableProperty]
- private string? _statusMessage;
-
- public SettingsViewModel(CardDatabase database)
- {
- _database = database;
- }
-
- [RelayCommand]
- private async Task LoadAsync()
- {
- CardCount = await _database.GetCardCountAsync();
- OracleCount = await _database.GetOracleCountAsync();
- SetCount = await _database.GetSetCountAsync();
- StatusMessage = $"Database ready: {CardCount:N0} cards, {OracleCount:N0} oracles, {SetCount:N0} sets";
- }
-}
diff --git a/src/Scry.App/Views/CardDetailPage.xaml b/src/Scry.App/Views/CardDetailPage.xaml
deleted file mode 100644
index 4432868..0000000
--- a/src/Scry.App/Views/CardDetailPage.xaml
+++ /dev/null
@@ -1,145 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Views/CardDetailPage.xaml.cs b/src/Scry.App/Views/CardDetailPage.xaml.cs
deleted file mode 100644
index 9690709..0000000
--- a/src/Scry.App/Views/CardDetailPage.xaml.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Scry.ViewModels;
-
-namespace Scry.Views;
-
-public partial class CardDetailPage : ContentPage
-{
- public CardDetailPage(CardDetailViewModel viewModel)
- {
- InitializeComponent();
- BindingContext = viewModel;
- }
-}
diff --git a/src/Scry.App/Views/CollectionPage.xaml b/src/Scry.App/Views/CollectionPage.xaml
deleted file mode 100644
index 70c7e3e..0000000
--- a/src/Scry.App/Views/CollectionPage.xaml
+++ /dev/null
@@ -1,156 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Views/CollectionPage.xaml.cs b/src/Scry.App/Views/CollectionPage.xaml.cs
deleted file mode 100644
index aa5e479..0000000
--- a/src/Scry.App/Views/CollectionPage.xaml.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Scry.ViewModels;
-
-namespace Scry.Views;
-
-public partial class CollectionPage : ContentPage
-{
- private readonly CollectionViewModel _viewModel;
-
- public CollectionPage(CollectionViewModel viewModel)
- {
- InitializeComponent();
- BindingContext = _viewModel = viewModel;
- }
-
- protected override void OnAppearing()
- {
- base.OnAppearing();
- _viewModel.LoadCollectionCommand.Execute(null);
- }
-}
diff --git a/src/Scry.App/Views/ScanPage.xaml b/src/Scry.App/Views/ScanPage.xaml
deleted file mode 100644
index b00bd94..0000000
--- a/src/Scry.App/Views/ScanPage.xaml
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Views/ScanPage.xaml.cs b/src/Scry.App/Views/ScanPage.xaml.cs
deleted file mode 100644
index 9122e6f..0000000
--- a/src/Scry.App/Views/ScanPage.xaml.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Scry.ViewModels;
-
-namespace Scry.Views;
-
-public partial class ScanPage : ContentPage
-{
- public ScanPage(ScanViewModel viewModel)
- {
- InitializeComponent();
- BindingContext = viewModel;
- }
-
- protected override async void OnAppearing()
- {
- base.OnAppearing();
-
- // Request camera permission
- var status = await Permissions.CheckStatusAsync();
- if (status != PermissionStatus.Granted)
- {
- status = await Permissions.RequestAsync();
- if (status != PermissionStatus.Granted)
- {
- await DisplayAlertAsync("Permission Denied", "Camera permission is required to scan cards.", "OK");
- return;
- }
- }
-
- // Start camera when page appears
- try
- {
- await cameraView.StartCameraPreview(CancellationToken.None);
- }
- catch (Exception ex)
- {
- await DisplayAlertAsync("Camera Error", $"Could not start camera: {ex.Message}", "OK");
- }
- }
-
- protected override void OnDisappearing()
- {
- base.OnDisappearing();
-
- // Stop camera when leaving page
- try
- {
- cameraView.StopCameraPreview();
- }
- catch
- {
- // Ignore cleanup errors
- }
- }
-}
diff --git a/src/Scry.App/Views/SettingsPage.xaml b/src/Scry.App/Views/SettingsPage.xaml
deleted file mode 100644
index aa27e91..0000000
--- a/src/Scry.App/Views/SettingsPage.xaml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.App/Views/SettingsPage.xaml.cs b/src/Scry.App/Views/SettingsPage.xaml.cs
deleted file mode 100644
index 64f843d..0000000
--- a/src/Scry.App/Views/SettingsPage.xaml.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Scry.ViewModels;
-
-namespace Scry.Views;
-
-public partial class SettingsPage : ContentPage
-{
- public SettingsPage(SettingsViewModel viewModel)
- {
- InitializeComponent();
- BindingContext = viewModel;
- }
-
- protected override async void OnAppearing()
- {
- base.OnAppearing();
-
- if (BindingContext is SettingsViewModel vm)
- {
- await vm.LoadCommand.ExecuteAsync(null);
- }
- }
-}
diff --git a/src/Scry.App/global.json b/src/Scry.App/global.json
deleted file mode 100644
index bb62055..0000000
--- a/src/Scry.App/global.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "sdk": {
- "version": "10.0.102",
- "rollForward": "latestMinor"
- }
-}
diff --git a/src/Scry.Core/Data/CardDatabase.cs b/src/Scry.Core/Data/CardDatabase.cs
deleted file mode 100644
index 23a3dac..0000000
--- a/src/Scry.Core/Data/CardDatabase.cs
+++ /dev/null
@@ -1,739 +0,0 @@
-using Microsoft.Data.Sqlite;
-using Scry.Core.Models;
-
-namespace Scry.Core.Data;
-
-///
-/// SQLite database for storing card data and perceptual hashes.
-/// Schema mirrors Scryfall's data model: oracles (game cards), sets, and cards (printings).
-///
-public class CardDatabase : IDisposable
-{
- private readonly SqliteConnection _connection;
- private readonly string _dbPath;
-
- public CardDatabase(string dbPath)
- {
- _dbPath = dbPath;
- _connection = new SqliteConnection($"Data Source={dbPath}");
- _connection.Open();
- Initialize();
- }
-
- private void Initialize()
- {
- using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- -- Abstract game cards (oracle)
- CREATE TABLE IF NOT EXISTS oracles (
- id TEXT PRIMARY KEY,
- name TEXT NOT NULL,
- mana_cost TEXT,
- cmc REAL,
- type_line TEXT,
- oracle_text TEXT,
- colors TEXT,
- color_identity TEXT,
- keywords TEXT,
- reserved INTEGER DEFAULT 0,
- legalities TEXT,
- power TEXT,
- toughness TEXT
- );
-
- CREATE INDEX IF NOT EXISTS idx_oracles_name ON oracles(name);
-
- -- MTG sets
- CREATE TABLE IF NOT EXISTS sets (
- id TEXT PRIMARY KEY,
- code TEXT NOT NULL UNIQUE,
- name TEXT NOT NULL,
- set_type TEXT,
- released_at TEXT,
- card_count INTEGER,
- icon_svg_uri TEXT,
- digital INTEGER DEFAULT 0,
- parent_set_code TEXT,
- block TEXT
- );
-
- CREATE INDEX IF NOT EXISTS idx_sets_code ON sets(code);
-
- -- Card printings with hashes
- CREATE TABLE IF NOT EXISTS cards (
- id TEXT PRIMARY KEY,
- oracle_id TEXT NOT NULL,
- set_id TEXT NOT NULL,
- set_code TEXT,
- name TEXT NOT NULL,
- collector_number TEXT,
- rarity TEXT,
- artist TEXT,
- illustration_id TEXT,
- image_uri TEXT,
- hash BLOB,
- lang TEXT DEFAULT 'en',
- prices_usd REAL,
- prices_usd_foil REAL,
- FOREIGN KEY (oracle_id) REFERENCES oracles(id),
- FOREIGN KEY (set_id) REFERENCES sets(id)
- );
-
- CREATE INDEX IF NOT EXISTS idx_cards_oracle_id ON cards(oracle_id);
- CREATE INDEX IF NOT EXISTS idx_cards_set_id ON cards(set_id);
- CREATE INDEX IF NOT EXISTS idx_cards_name ON cards(name);
- CREATE INDEX IF NOT EXISTS idx_cards_set_code ON cards(set_code);
-
- -- Metadata for tracking sync state
- CREATE TABLE IF NOT EXISTS metadata (
- key TEXT PRIMARY KEY,
- value TEXT NOT NULL
- );
- """;
- cmd.ExecuteNonQuery();
- }
-
- #region Metadata
-
- public async Task GetMetadataAsync(string key, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT value FROM metadata WHERE key = $key";
- cmd.Parameters.AddWithValue("$key", key);
-
- var result = await cmd.ExecuteScalarAsync(ct);
- return result as string;
- }
-
- public async Task SetMetadataAsync(string key, string value, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO metadata (key, value) VALUES ($key, $value)
- """;
- cmd.Parameters.AddWithValue("$key", key);
- cmd.Parameters.AddWithValue("$value", value);
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- #endregion
-
- #region Oracles
-
- public async Task InsertOracleAsync(Oracle oracle, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO oracles
- (id, name, mana_cost, cmc, type_line, oracle_text, colors, color_identity, keywords, reserved, legalities, power, toughness)
- VALUES ($id, $name, $mana_cost, $cmc, $type_line, $oracle_text, $colors, $color_identity, $keywords, $reserved, $legalities, $power, $toughness)
- """;
- cmd.Parameters.AddWithValue("$id", oracle.Id);
- cmd.Parameters.AddWithValue("$name", oracle.Name);
- cmd.Parameters.AddWithValue("$mana_cost", oracle.ManaCost ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$cmc", oracle.Cmc ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$type_line", oracle.TypeLine ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$oracle_text", oracle.OracleText ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$colors", oracle.Colors ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$color_identity", oracle.ColorIdentity ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$keywords", oracle.Keywords ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$reserved", oracle.Reserved ? 1 : 0);
- cmd.Parameters.AddWithValue("$legalities", oracle.Legalities ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$power", oracle.Power ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$toughness", oracle.Toughness ?? (object)DBNull.Value);
-
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- public async Task InsertOracleBatchAsync(IEnumerable oracles, CancellationToken ct = default)
- {
- await using var transaction = await _connection.BeginTransactionAsync(ct);
-
- try
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO oracles
- (id, name, mana_cost, cmc, type_line, oracle_text, colors, color_identity, keywords, reserved, legalities, power, toughness)
- VALUES ($id, $name, $mana_cost, $cmc, $type_line, $oracle_text, $colors, $color_identity, $keywords, $reserved, $legalities, $power, $toughness)
- """;
-
- var idParam = cmd.Parameters.Add("$id", SqliteType.Text);
- var nameParam = cmd.Parameters.Add("$name", SqliteType.Text);
- var manaCostParam = cmd.Parameters.Add("$mana_cost", SqliteType.Text);
- var cmcParam = cmd.Parameters.Add("$cmc", SqliteType.Real);
- var typeLineParam = cmd.Parameters.Add("$type_line", SqliteType.Text);
- var oracleTextParam = cmd.Parameters.Add("$oracle_text", SqliteType.Text);
- var colorsParam = cmd.Parameters.Add("$colors", SqliteType.Text);
- var colorIdentityParam = cmd.Parameters.Add("$color_identity", SqliteType.Text);
- var keywordsParam = cmd.Parameters.Add("$keywords", SqliteType.Text);
- var reservedParam = cmd.Parameters.Add("$reserved", SqliteType.Integer);
- var legalitiesParam = cmd.Parameters.Add("$legalities", SqliteType.Text);
- var powerParam = cmd.Parameters.Add("$power", SqliteType.Text);
- var toughnessParam = cmd.Parameters.Add("$toughness", SqliteType.Text);
-
- foreach (var oracle in oracles)
- {
- ct.ThrowIfCancellationRequested();
-
- idParam.Value = oracle.Id;
- nameParam.Value = oracle.Name;
- manaCostParam.Value = oracle.ManaCost ?? (object)DBNull.Value;
- cmcParam.Value = oracle.Cmc ?? (object)DBNull.Value;
- typeLineParam.Value = oracle.TypeLine ?? (object)DBNull.Value;
- oracleTextParam.Value = oracle.OracleText ?? (object)DBNull.Value;
- colorsParam.Value = oracle.Colors ?? (object)DBNull.Value;
- colorIdentityParam.Value = oracle.ColorIdentity ?? (object)DBNull.Value;
- keywordsParam.Value = oracle.Keywords ?? (object)DBNull.Value;
- reservedParam.Value = oracle.Reserved ? 1 : 0;
- legalitiesParam.Value = oracle.Legalities ?? (object)DBNull.Value;
- powerParam.Value = oracle.Power ?? (object)DBNull.Value;
- toughnessParam.Value = oracle.Toughness ?? (object)DBNull.Value;
-
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- await transaction.CommitAsync(ct);
- }
- catch
- {
- await transaction.RollbackAsync(ct);
- throw;
- }
- }
-
- public async Task GetOracleByIdAsync(string id, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, name, mana_cost, cmc, type_line, oracle_text, colors, color_identity, keywords, reserved, legalities, power, toughness
- FROM oracles WHERE id = $id
- """;
- cmd.Parameters.AddWithValue("$id", id);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- if (await reader.ReadAsync(ct))
- {
- return ReadOracle(reader);
- }
- return null;
- }
-
- public async Task GetOracleByNameAsync(string name, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, name, mana_cost, cmc, type_line, oracle_text, colors, color_identity, keywords, reserved, legalities, power, toughness
- FROM oracles WHERE name = $name COLLATE NOCASE
- """;
- cmd.Parameters.AddWithValue("$name", name);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- if (await reader.ReadAsync(ct))
- {
- return ReadOracle(reader);
- }
- return null;
- }
-
- public async Task> GetExistingOracleIdsAsync(CancellationToken ct = default)
- {
- var ids = new HashSet();
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT id FROM oracles";
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- ids.Add(reader.GetString(0));
- }
-
- return ids;
- }
-
- public async Task GetOracleCountAsync(CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT COUNT(*) FROM oracles";
- var result = await cmd.ExecuteScalarAsync(ct);
- return Convert.ToInt32(result);
- }
-
- private static Oracle ReadOracle(SqliteDataReader reader) => new()
- {
- Id = reader.GetString(0),
- Name = reader.GetString(1),
- ManaCost = reader.IsDBNull(2) ? null : reader.GetString(2),
- Cmc = reader.IsDBNull(3) ? null : reader.GetDouble(3),
- TypeLine = reader.IsDBNull(4) ? null : reader.GetString(4),
- OracleText = reader.IsDBNull(5) ? null : reader.GetString(5),
- Colors = reader.IsDBNull(6) ? null : reader.GetString(6),
- ColorIdentity = reader.IsDBNull(7) ? null : reader.GetString(7),
- Keywords = reader.IsDBNull(8) ? null : reader.GetString(8),
- Reserved = reader.GetInt32(9) != 0,
- Legalities = reader.IsDBNull(10) ? null : reader.GetString(10),
- Power = reader.IsDBNull(11) ? null : reader.GetString(11),
- Toughness = reader.IsDBNull(12) ? null : reader.GetString(12),
- };
-
- #endregion
-
- #region Sets
-
- public async Task InsertSetAsync(Set set, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO sets
- (id, code, name, set_type, released_at, card_count, icon_svg_uri, digital, parent_set_code, block)
- VALUES ($id, $code, $name, $set_type, $released_at, $card_count, $icon_svg_uri, $digital, $parent_set_code, $block)
- """;
- cmd.Parameters.AddWithValue("$id", set.Id);
- cmd.Parameters.AddWithValue("$code", set.Code);
- cmd.Parameters.AddWithValue("$name", set.Name);
- cmd.Parameters.AddWithValue("$set_type", set.SetType ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$released_at", set.ReleasedAt ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$card_count", set.CardCount ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$icon_svg_uri", set.IconSvgUri ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$digital", set.Digital ? 1 : 0);
- cmd.Parameters.AddWithValue("$parent_set_code", set.ParentSetCode ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$block", set.Block ?? (object)DBNull.Value);
-
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- public async Task InsertSetBatchAsync(IEnumerable sets, CancellationToken ct = default)
- {
- await using var transaction = await _connection.BeginTransactionAsync(ct);
-
- try
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO sets
- (id, code, name, set_type, released_at, card_count, icon_svg_uri, digital, parent_set_code, block)
- VALUES ($id, $code, $name, $set_type, $released_at, $card_count, $icon_svg_uri, $digital, $parent_set_code, $block)
- """;
-
- var idParam = cmd.Parameters.Add("$id", SqliteType.Text);
- var codeParam = cmd.Parameters.Add("$code", SqliteType.Text);
- var nameParam = cmd.Parameters.Add("$name", SqliteType.Text);
- var setTypeParam = cmd.Parameters.Add("$set_type", SqliteType.Text);
- var releasedAtParam = cmd.Parameters.Add("$released_at", SqliteType.Text);
- var cardCountParam = cmd.Parameters.Add("$card_count", SqliteType.Integer);
- var iconSvgUriParam = cmd.Parameters.Add("$icon_svg_uri", SqliteType.Text);
- var digitalParam = cmd.Parameters.Add("$digital", SqliteType.Integer);
- var parentSetCodeParam = cmd.Parameters.Add("$parent_set_code", SqliteType.Text);
- var blockParam = cmd.Parameters.Add("$block", SqliteType.Text);
-
- foreach (var set in sets)
- {
- ct.ThrowIfCancellationRequested();
-
- idParam.Value = set.Id;
- codeParam.Value = set.Code;
- nameParam.Value = set.Name;
- setTypeParam.Value = set.SetType ?? (object)DBNull.Value;
- releasedAtParam.Value = set.ReleasedAt ?? (object)DBNull.Value;
- cardCountParam.Value = set.CardCount ?? (object)DBNull.Value;
- iconSvgUriParam.Value = set.IconSvgUri ?? (object)DBNull.Value;
- digitalParam.Value = set.Digital ? 1 : 0;
- parentSetCodeParam.Value = set.ParentSetCode ?? (object)DBNull.Value;
- blockParam.Value = set.Block ?? (object)DBNull.Value;
-
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- await transaction.CommitAsync(ct);
- }
- catch
- {
- await transaction.RollbackAsync(ct);
- throw;
- }
- }
-
- public async Task GetSetByIdAsync(string id, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, code, name, set_type, released_at, card_count, icon_svg_uri, digital, parent_set_code, block
- FROM sets WHERE id = $id
- """;
- cmd.Parameters.AddWithValue("$id", id);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- if (await reader.ReadAsync(ct))
- {
- return ReadSet(reader);
- }
- return null;
- }
-
- public async Task GetSetByCodeAsync(string code, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, code, name, set_type, released_at, card_count, icon_svg_uri, digital, parent_set_code, block
- FROM sets WHERE code = $code COLLATE NOCASE
- """;
- cmd.Parameters.AddWithValue("$code", code);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- if (await reader.ReadAsync(ct))
- {
- return ReadSet(reader);
- }
- return null;
- }
-
- public async Task> GetExistingSetIdsAsync(CancellationToken ct = default)
- {
- var ids = new HashSet();
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT id FROM sets";
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- ids.Add(reader.GetString(0));
- }
-
- return ids;
- }
-
- public async Task GetSetCountAsync(CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT COUNT(*) FROM sets";
- var result = await cmd.ExecuteScalarAsync(ct);
- return Convert.ToInt32(result);
- }
-
- private static Set ReadSet(SqliteDataReader reader) => new()
- {
- Id = reader.GetString(0),
- Code = reader.GetString(1),
- Name = reader.GetString(2),
- SetType = reader.IsDBNull(3) ? null : reader.GetString(3),
- ReleasedAt = reader.IsDBNull(4) ? null : reader.GetString(4),
- CardCount = reader.IsDBNull(5) ? null : reader.GetInt32(5),
- IconSvgUri = reader.IsDBNull(6) ? null : reader.GetString(6),
- Digital = reader.GetInt32(7) != 0,
- ParentSetCode = reader.IsDBNull(8) ? null : reader.GetString(8),
- Block = reader.IsDBNull(9) ? null : reader.GetString(9),
- };
-
- #endregion
-
- #region Cards (Printings)
-
- public async Task InsertCardAsync(Card card, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO cards
- (id, oracle_id, set_id, set_code, name, collector_number, rarity, artist, illustration_id, image_uri, hash, lang, prices_usd, prices_usd_foil)
- VALUES ($id, $oracle_id, $set_id, $set_code, $name, $collector_number, $rarity, $artist, $illustration_id, $image_uri, $hash, $lang, $prices_usd, $prices_usd_foil)
- """;
- cmd.Parameters.AddWithValue("$id", card.Id);
- cmd.Parameters.AddWithValue("$oracle_id", card.OracleId);
- cmd.Parameters.AddWithValue("$set_id", card.SetId);
- cmd.Parameters.AddWithValue("$set_code", card.SetCode ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$name", card.Name);
- cmd.Parameters.AddWithValue("$collector_number", card.CollectorNumber ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$rarity", card.Rarity ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$artist", card.Artist ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$illustration_id", card.IllustrationId ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$image_uri", card.ImageUri ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$hash", card.Hash ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$lang", card.Lang ?? "en");
- cmd.Parameters.AddWithValue("$prices_usd", card.PricesUsd ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("$prices_usd_foil", card.PricesUsdFoil ?? (object)DBNull.Value);
-
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- public async Task InsertCardBatchAsync(IEnumerable cards, CancellationToken ct = default)
- {
- await using var transaction = await _connection.BeginTransactionAsync(ct);
-
- try
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- INSERT OR REPLACE INTO cards
- (id, oracle_id, set_id, set_code, name, collector_number, rarity, artist, illustration_id, image_uri, hash, lang, prices_usd, prices_usd_foil)
- VALUES ($id, $oracle_id, $set_id, $set_code, $name, $collector_number, $rarity, $artist, $illustration_id, $image_uri, $hash, $lang, $prices_usd, $prices_usd_foil)
- """;
-
- var idParam = cmd.Parameters.Add("$id", SqliteType.Text);
- var oracleIdParam = cmd.Parameters.Add("$oracle_id", SqliteType.Text);
- var setIdParam = cmd.Parameters.Add("$set_id", SqliteType.Text);
- var setCodeParam = cmd.Parameters.Add("$set_code", SqliteType.Text);
- var nameParam = cmd.Parameters.Add("$name", SqliteType.Text);
- var collectorNumberParam = cmd.Parameters.Add("$collector_number", SqliteType.Text);
- var rarityParam = cmd.Parameters.Add("$rarity", SqliteType.Text);
- var artistParam = cmd.Parameters.Add("$artist", SqliteType.Text);
- var illustrationIdParam = cmd.Parameters.Add("$illustration_id", SqliteType.Text);
- var imageUriParam = cmd.Parameters.Add("$image_uri", SqliteType.Text);
- var hashParam = cmd.Parameters.Add("$hash", SqliteType.Blob);
- var langParam = cmd.Parameters.Add("$lang", SqliteType.Text);
- var pricesUsdParam = cmd.Parameters.Add("$prices_usd", SqliteType.Real);
- var pricesUsdFoilParam = cmd.Parameters.Add("$prices_usd_foil", SqliteType.Real);
-
- foreach (var card in cards)
- {
- ct.ThrowIfCancellationRequested();
-
- idParam.Value = card.Id;
- oracleIdParam.Value = card.OracleId;
- setIdParam.Value = card.SetId;
- setCodeParam.Value = card.SetCode ?? (object)DBNull.Value;
- nameParam.Value = card.Name;
- collectorNumberParam.Value = card.CollectorNumber ?? (object)DBNull.Value;
- rarityParam.Value = card.Rarity ?? (object)DBNull.Value;
- artistParam.Value = card.Artist ?? (object)DBNull.Value;
- illustrationIdParam.Value = card.IllustrationId ?? (object)DBNull.Value;
- imageUriParam.Value = card.ImageUri ?? (object)DBNull.Value;
- hashParam.Value = card.Hash ?? (object)DBNull.Value;
- langParam.Value = card.Lang ?? "en";
- pricesUsdParam.Value = card.PricesUsd ?? (object)DBNull.Value;
- pricesUsdFoilParam.Value = card.PricesUsdFoil ?? (object)DBNull.Value;
-
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- await transaction.CommitAsync(ct);
- }
- catch
- {
- await transaction.RollbackAsync(ct);
- throw;
- }
- }
-
- public async Task GetCardByIdAsync(string id, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, oracle_id, set_id, set_code, name, collector_number, rarity, artist, illustration_id, image_uri, hash, lang, prices_usd, prices_usd_foil
- FROM cards WHERE id = $id
- """;
- cmd.Parameters.AddWithValue("$id", id);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- if (await reader.ReadAsync(ct))
- {
- return ReadCard(reader);
- }
- return null;
- }
-
- public async Task> GetCardsByOracleIdAsync(string oracleId, CancellationToken ct = default)
- {
- var cards = new List();
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, oracle_id, set_id, set_code, name, collector_number, rarity, artist, illustration_id, image_uri, hash, lang, prices_usd, prices_usd_foil
- FROM cards WHERE oracle_id = $oracle_id
- """;
- cmd.Parameters.AddWithValue("$oracle_id", oracleId);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- cards.Add(ReadCard(reader));
- }
-
- return cards;
- }
-
- public async Task> GetCardsByNameAsync(string name, CancellationToken ct = default)
- {
- var cards = new List();
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, oracle_id, set_id, set_code, name, collector_number, rarity, artist, illustration_id, image_uri, hash, lang, prices_usd, prices_usd_foil
- FROM cards WHERE name = $name COLLATE NOCASE
- """;
- cmd.Parameters.AddWithValue("$name", name);
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- cards.Add(ReadCard(reader));
- }
-
- return cards;
- }
-
- public async Task> GetAllCardsAsync(CancellationToken ct = default)
- {
- var cards = new List();
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- SELECT id, oracle_id, set_id, set_code, name, collector_number, rarity, artist, illustration_id, image_uri, hash, lang, prices_usd, prices_usd_foil
- FROM cards
- """;
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- cards.Add(ReadCard(reader));
- }
-
- return cards;
- }
-
- public async Task> GetCardsWithHashAsync(CancellationToken ct = default)
- {
- var cards = new List();
-
- await using var cmd = _connection.CreateCommand();
- // Join with oracles and sets to get denormalized data
- cmd.CommandText = """
- SELECT c.id, c.oracle_id, c.set_id, c.set_code, c.name, c.collector_number, c.rarity, c.artist,
- c.illustration_id, c.image_uri, c.hash, c.lang, c.prices_usd, c.prices_usd_foil,
- o.mana_cost, o.type_line, o.oracle_text, o.power, o.toughness,
- s.name as set_name
- FROM cards c
- LEFT JOIN oracles o ON c.oracle_id = o.id
- LEFT JOIN sets s ON c.set_id = s.id
- WHERE c.hash IS NOT NULL
- """;
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- cards.Add(ReadCardWithOracle(reader));
- }
-
- return cards;
- }
-
- public async Task> GetExistingCardIdsAsync(CancellationToken ct = default)
- {
- var ids = new HashSet();
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT id FROM cards";
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- ids.Add(reader.GetString(0));
- }
-
- return ids;
- }
-
- public async Task> GetExistingCardNamesAsync(CancellationToken ct = default)
- {
- var names = new HashSet(StringComparer.OrdinalIgnoreCase);
-
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT DISTINCT name FROM cards";
-
- await using var reader = await cmd.ExecuteReaderAsync(ct);
- while (await reader.ReadAsync(ct))
- {
- names.Add(reader.GetString(0));
- }
-
- return names;
- }
-
- public async Task GetCardCountAsync(CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "SELECT COUNT(*) FROM cards";
- var result = await cmd.ExecuteScalarAsync(ct);
- return Convert.ToInt32(result);
- }
-
- public async Task DeleteCardByIdAsync(string id, CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "DELETE FROM cards WHERE id = $id";
- cmd.Parameters.AddWithValue("$id", id);
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- public async Task ClearCardsAsync(CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = "DELETE FROM cards";
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- public async Task ClearAllAsync(CancellationToken ct = default)
- {
- await using var cmd = _connection.CreateCommand();
- cmd.CommandText = """
- DELETE FROM cards;
- DELETE FROM oracles;
- DELETE FROM sets;
- DELETE FROM metadata;
- """;
- await cmd.ExecuteNonQueryAsync(ct);
- }
-
- private static Card ReadCard(SqliteDataReader reader) => new()
- {
- Id = reader.GetString(0),
- OracleId = reader.GetString(1),
- SetId = reader.GetString(2),
- SetCode = reader.IsDBNull(3) ? null : reader.GetString(3),
- Name = reader.GetString(4),
- CollectorNumber = reader.IsDBNull(5) ? null : reader.GetString(5),
- Rarity = reader.IsDBNull(6) ? null : reader.GetString(6),
- Artist = reader.IsDBNull(7) ? null : reader.GetString(7),
- IllustrationId = reader.IsDBNull(8) ? null : reader.GetString(8),
- ImageUri = reader.IsDBNull(9) ? null : reader.GetString(9),
- Hash = reader.IsDBNull(10) ? null : (byte[])reader.GetValue(10),
- Lang = reader.IsDBNull(11) ? null : reader.GetString(11),
- PricesUsd = reader.IsDBNull(12) ? null : (decimal)reader.GetDouble(12),
- PricesUsdFoil = reader.IsDBNull(13) ? null : (decimal)reader.GetDouble(13),
- };
-
- ///
- /// Reads a card with joined Oracle and Set data (columns 14-19).
- ///
- private static Card ReadCardWithOracle(SqliteDataReader reader) => new()
- {
- Id = reader.GetString(0),
- OracleId = reader.GetString(1),
- SetId = reader.GetString(2),
- SetCode = reader.IsDBNull(3) ? null : reader.GetString(3),
- Name = reader.GetString(4),
- CollectorNumber = reader.IsDBNull(5) ? null : reader.GetString(5),
- Rarity = reader.IsDBNull(6) ? null : reader.GetString(6),
- Artist = reader.IsDBNull(7) ? null : reader.GetString(7),
- IllustrationId = reader.IsDBNull(8) ? null : reader.GetString(8),
- ImageUri = reader.IsDBNull(9) ? null : reader.GetString(9),
- Hash = reader.IsDBNull(10) ? null : (byte[])reader.GetValue(10),
- Lang = reader.IsDBNull(11) ? null : reader.GetString(11),
- PricesUsd = reader.IsDBNull(12) ? null : (decimal)reader.GetDouble(12),
- PricesUsdFoil = reader.IsDBNull(13) ? null : (decimal)reader.GetDouble(13),
- // Denormalized Oracle fields (from JOIN)
- ManaCost = reader.IsDBNull(14) ? null : reader.GetString(14),
- TypeLine = reader.IsDBNull(15) ? null : reader.GetString(15),
- OracleText = reader.IsDBNull(16) ? null : reader.GetString(16),
- Power = reader.IsDBNull(17) ? null : reader.GetString(17),
- Toughness = reader.IsDBNull(18) ? null : reader.GetString(18),
- SetName = reader.IsDBNull(19) ? null : reader.GetString(19),
- };
-
- #endregion
-
- public void Dispose()
- {
- _connection.Dispose();
- }
-}
diff --git a/src/Scry.Core/Imaging/CardDetector.cs b/src/Scry.Core/Imaging/CardDetector.cs
deleted file mode 100644
index 5b147eb..0000000
--- a/src/Scry.Core/Imaging/CardDetector.cs
+++ /dev/null
@@ -1,625 +0,0 @@
-using SkiaSharp;
-
-namespace Scry.Core.Imaging;
-
-///
-/// Detects card boundaries in images using edge detection and contour analysis.
-///
-public static class CardDetector
-{
- ///
- /// Standard MTG card aspect ratio (height / width).
- /// Cards are 63mm x 88mm = 1.397 aspect ratio.
- ///
- private const float CardAspectRatio = 88f / 63f; // ~1.397
- private const float AspectRatioTolerance = 0.25f; // Allow 25% deviation
-
- ///
- /// Minimum area of detected card relative to image area.
- ///
- private const float MinCardAreaRatio = 0.05f; // Card must be at least 5% of image
-
- ///
- /// Maximum area of detected card relative to image area.
- ///
- private const float MaxCardAreaRatio = 0.98f; // Card can be at most 98% of image
-
- ///
- /// Result of card detection.
- ///
- public record CardDetectionResult(
- bool Found,
- SKPoint[] Corners,
- float Confidence,
- string? DebugMessage = null
- )
- {
- public static CardDetectionResult NotFound(string reason) =>
- new(false, Array.Empty(), 0, reason);
-
- public static CardDetectionResult Success(SKPoint[] corners, float confidence) =>
- new(true, corners, confidence);
- }
-
- ///
- /// Detect a card in the image and return its corner points.
- ///
- public static CardDetectionResult DetectCard(SKBitmap image)
- {
- // Step 1: Convert to grayscale
- using var grayscale = ToGrayscale(image);
-
- // Step 2: Apply Gaussian blur to reduce noise
- using var blurred = ApplyGaussianBlur(grayscale, 5);
-
- // Step 3: Apply Canny edge detection
- using var edges = ApplyCannyEdgeDetection(blurred, 50, 150);
-
- // Step 4: Find contours
- var contours = FindContours(edges);
-
- if (contours.Count == 0)
- return CardDetectionResult.NotFound("No contours found");
-
- // Step 5: Find the best card-like quadrilateral
- var imageArea = image.Width * image.Height;
- var bestQuad = FindBestCardQuadrilateral(contours, imageArea);
-
- if (bestQuad == null)
- return CardDetectionResult.NotFound("No card-like quadrilateral found");
-
- // Step 6: Order corners consistently (top-left, top-right, bottom-right, bottom-left)
- var orderedCorners = OrderCorners(bestQuad);
-
- // Calculate confidence based on how well the shape matches a card
- var confidence = CalculateConfidence(orderedCorners, imageArea);
-
- return CardDetectionResult.Success(orderedCorners, confidence);
- }
-
- ///
- /// Convert image to grayscale.
- ///
- private static SKBitmap ToGrayscale(SKBitmap source)
- {
- var result = new SKBitmap(source.Width, source.Height, SKColorType.Gray8, SKAlphaType.Opaque);
-
- for (var y = 0; y < source.Height; y++)
- {
- for (var x = 0; x < source.Width; x++)
- {
- var pixel = source.GetPixel(x, y);
- var gray = (byte)(0.299 * pixel.Red + 0.587 * pixel.Green + 0.114 * pixel.Blue);
- result.SetPixel(x, y, new SKColor(gray, gray, gray));
- }
- }
-
- return result;
- }
-
- ///
- /// Apply Gaussian blur using a simple box blur approximation.
- ///
- private static SKBitmap ApplyGaussianBlur(SKBitmap source, int radius)
- {
- var result = new SKBitmap(source.Width, source.Height, source.ColorType, source.AlphaType);
-
- // Use SkiaSharp's built-in blur
- using var surface = SKSurface.Create(new SKImageInfo(source.Width, source.Height));
- var canvas = surface.Canvas;
-
- using var paint = new SKPaint
- {
- ImageFilter = SKImageFilter.CreateBlur(radius, radius)
- };
-
- canvas.DrawBitmap(source, 0, 0, paint);
-
- using var image = surface.Snapshot();
- image.ReadPixels(result.Info, result.GetPixels(), result.RowBytes, 0, 0);
-
- return result;
- }
-
- ///
- /// Apply Canny edge detection.
- ///
- private static SKBitmap ApplyCannyEdgeDetection(SKBitmap source, int lowThreshold, int highThreshold)
- {
- var width = source.Width;
- var height = source.Height;
-
- // Step 1: Compute gradients using Sobel operators
- var gradientX = new float[height, width];
- var gradientY = new float[height, width];
- var magnitude = new float[height, width];
- var direction = new float[height, width];
-
- // Sobel kernels
- int[,] sobelX = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
- int[,] sobelY = { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
-
- for (var y = 1; y < height - 1; y++)
- {
- for (var x = 1; x < width - 1; x++)
- {
- float gx = 0, gy = 0;
-
- for (var ky = -1; ky <= 1; ky++)
- {
- for (var kx = -1; kx <= 1; kx++)
- {
- var pixel = source.GetPixel(x + kx, y + ky).Red;
- gx += pixel * sobelX[ky + 1, kx + 1];
- gy += pixel * sobelY[ky + 1, kx + 1];
- }
- }
-
- gradientX[y, x] = gx;
- gradientY[y, x] = gy;
- magnitude[y, x] = MathF.Sqrt(gx * gx + gy * gy);
- direction[y, x] = MathF.Atan2(gy, gx);
- }
- }
-
- // Step 2: Non-maximum suppression
- var suppressed = new float[height, width];
-
- for (var y = 1; y < height - 1; y++)
- {
- for (var x = 1; x < width - 1; x++)
- {
- var angle = direction[y, x] * 180 / MathF.PI;
- if (angle < 0) angle += 180;
-
- float neighbor1, neighbor2;
-
- if (angle is < 22.5f or >= 157.5f)
- {
- neighbor1 = magnitude[y, x - 1];
- neighbor2 = magnitude[y, x + 1];
- }
- else if (angle is >= 22.5f and < 67.5f)
- {
- neighbor1 = magnitude[y - 1, x + 1];
- neighbor2 = magnitude[y + 1, x - 1];
- }
- else if (angle is >= 67.5f and < 112.5f)
- {
- neighbor1 = magnitude[y - 1, x];
- neighbor2 = magnitude[y + 1, x];
- }
- else
- {
- neighbor1 = magnitude[y - 1, x - 1];
- neighbor2 = magnitude[y + 1, x + 1];
- }
-
- if (magnitude[y, x] >= neighbor1 && magnitude[y, x] >= neighbor2)
- suppressed[y, x] = magnitude[y, x];
- }
- }
-
- // Step 3: Double thresholding and edge tracking
- var result = new SKBitmap(width, height, SKColorType.Gray8, SKAlphaType.Opaque);
- var strong = new bool[height, width];
- var weak = new bool[height, width];
-
- for (var y = 0; y < height; y++)
- {
- for (var x = 0; x < width; x++)
- {
- if (suppressed[y, x] >= highThreshold)
- strong[y, x] = true;
- else if (suppressed[y, x] >= lowThreshold)
- weak[y, x] = true;
- }
- }
-
- // Edge tracking by hysteresis
- for (var y = 1; y < height - 1; y++)
- {
- for (var x = 1; x < width - 1; x++)
- {
- byte value = 0;
-
- if (strong[y, x])
- {
- value = 255;
- }
- else if (weak[y, x])
- {
- // Check if connected to strong edge
- for (var dy = -1; dy <= 1; dy++)
- {
- for (var dx = -1; dx <= 1; dx++)
- {
- if (strong[y + dy, x + dx])
- {
- value = 255;
- break;
- }
- }
- if (value == 255) break;
- }
- }
-
- result.SetPixel(x, y, new SKColor(value, value, value));
- }
- }
-
- return result;
- }
-
- ///
- /// Find contours in a binary edge image.
- ///
- private static List> FindContours(SKBitmap edges)
- {
- var width = edges.Width;
- var height = edges.Height;
- var visited = new bool[height, width];
- var contours = new List>();
-
- // Simple contour tracing
- for (var y = 1; y < height - 1; y++)
- {
- for (var x = 1; x < width - 1; x++)
- {
- if (visited[y, x]) continue;
- if (edges.GetPixel(x, y).Red < 128) continue;
-
- var contour = TraceContour(edges, x, y, visited);
- if (contour.Count >= 4)
- {
- contours.Add(contour);
- }
- }
- }
-
- return contours;
- }
-
- ///
- /// Trace a contour starting from a point.
- ///
- private static List TraceContour(SKBitmap edges, int startX, int startY, bool[,] visited)
- {
- var contour = new List();
- var queue = new Queue<(int x, int y)>();
- queue.Enqueue((startX, startY));
-
- // 8-directional neighbors
- int[] dx = { -1, 0, 1, 1, 1, 0, -1, -1 };
- int[] dy = { -1, -1, -1, 0, 1, 1, 1, 0 };
-
- while (queue.Count > 0 && contour.Count < 10000) // Limit to prevent runaway
- {
- var (x, y) = queue.Dequeue();
-
- if (x < 0 || x >= edges.Width || y < 0 || y >= edges.Height)
- continue;
- if (visited[y, x])
- continue;
- if (edges.GetPixel(x, y).Red < 128)
- continue;
-
- visited[y, x] = true;
- contour.Add(new SKPoint(x, y));
-
- for (var i = 0; i < 8; i++)
- {
- queue.Enqueue((x + dx[i], y + dy[i]));
- }
- }
-
- return contour;
- }
-
- ///
- /// Find the best quadrilateral that matches a card shape.
- ///
- private static SKPoint[]? FindBestCardQuadrilateral(List> contours, float imageArea)
- {
- SKPoint[]? bestQuad = null;
- var bestScore = float.MinValue;
-
- foreach (var contour in contours)
- {
- // Simplify contour using Douglas-Peucker algorithm
- var simplified = SimplifyContour(contour, contour.Count * 0.02f);
-
- // Try to approximate as quadrilateral
- var quad = ApproximateQuadrilateral(simplified);
- if (quad == null) continue;
-
- // Check if it's a valid card shape
- var area = CalculateQuadArea(quad);
- var areaRatio = area / imageArea;
-
- if (areaRatio < MinCardAreaRatio || areaRatio > MaxCardAreaRatio)
- continue;
-
- // Check aspect ratio
- var aspectScore = CalculateAspectRatioScore(quad);
- if (aspectScore < 0.5f) continue;
-
- // Check if it's convex
- if (!IsConvex(quad)) continue;
-
- // Score based on area (prefer larger) and aspect ratio match
- var score = areaRatio * aspectScore;
-
- if (score > bestScore)
- {
- bestScore = score;
- bestQuad = quad;
- }
- }
-
- return bestQuad;
- }
-
- ///
- /// Simplify a contour using Douglas-Peucker algorithm.
- ///
- private static List SimplifyContour(List contour, float epsilon)
- {
- if (contour.Count < 3) return contour;
-
- // Find the point farthest from line between first and last
- var first = contour[0];
- var last = contour[^1];
-
- var maxDist = 0f;
- var maxIndex = 0;
-
- for (var i = 1; i < contour.Count - 1; i++)
- {
- var dist = PointToLineDistance(contour[i], first, last);
- if (dist > maxDist)
- {
- maxDist = dist;
- maxIndex = i;
- }
- }
-
- if (maxDist > epsilon)
- {
- var left = SimplifyContour(contour.Take(maxIndex + 1).ToList(), epsilon);
- var right = SimplifyContour(contour.Skip(maxIndex).ToList(), epsilon);
-
- return left.Take(left.Count - 1).Concat(right).ToList();
- }
-
- return new List { first, last };
- }
-
- ///
- /// Calculate distance from point to line.
- ///
- private static float PointToLineDistance(SKPoint point, SKPoint lineStart, SKPoint lineEnd)
- {
- var dx = lineEnd.X - lineStart.X;
- var dy = lineEnd.Y - lineStart.Y;
- var lengthSquared = dx * dx + dy * dy;
-
- if (lengthSquared == 0)
- return Distance(point, lineStart);
-
- var t = ((point.X - lineStart.X) * dx + (point.Y - lineStart.Y) * dy) / lengthSquared;
- t = Math.Clamp(t, 0, 1);
-
- var projection = new SKPoint(lineStart.X + t * dx, lineStart.Y + t * dy);
- return Distance(point, projection);
- }
-
- ///
- /// Try to approximate a contour as a quadrilateral.
- ///
- private static SKPoint[]? ApproximateQuadrilateral(List contour)
- {
- if (contour.Count < 4) return null;
-
- // If already 4 points, use them
- if (contour.Count == 4)
- return contour.ToArray();
-
- // Find convex hull
- var hull = ConvexHull(contour);
- if (hull.Count < 4) return null;
-
- // Find 4 corners by finding the 4 points that form the largest quadrilateral
- if (hull.Count == 4)
- return hull.ToArray();
-
- // Find the 4 most extreme points
- var corners = FindExtremePoints(hull);
- return corners.Length == 4 ? corners : null;
- }
-
- ///
- /// Calculate convex hull using Graham scan.
- ///
- private static List ConvexHull(List points)
- {
- if (points.Count < 3) return points;
-
- // Find bottom-most point (or left-most in case of tie)
- var start = points.OrderBy(p => p.Y).ThenBy(p => p.X).First();
-
- // Sort by polar angle
- var sorted = points
- .Where(p => p != start)
- .OrderBy(p => MathF.Atan2(p.Y - start.Y, p.X - start.X))
- .ThenBy(p => Distance(p, start))
- .ToList();
-
- var hull = new List { start };
-
- foreach (var point in sorted)
- {
- while (hull.Count > 1 && CrossProduct(hull[^2], hull[^1], point) <= 0)
- {
- hull.RemoveAt(hull.Count - 1);
- }
- hull.Add(point);
- }
-
- return hull;
- }
-
- ///
- /// Find the 4 most extreme points of a convex hull.
- ///
- private static SKPoint[] FindExtremePoints(List hull)
- {
- if (hull.Count <= 4) return hull.ToArray();
-
- // Find points with min/max X and Y
- var minX = hull.OrderBy(p => p.X).First();
- var maxX = hull.OrderByDescending(p => p.X).First();
- var minY = hull.OrderBy(p => p.Y).First();
- var maxY = hull.OrderByDescending(p => p.Y).First();
-
- var extremes = new HashSet { minX, maxX, minY, maxY };
-
- if (extremes.Count == 4)
- return extremes.ToArray();
-
- // If we have duplicates, add more points
- var sorted = hull.OrderBy(p => MathF.Atan2(p.Y - hull.Average(h => h.Y), p.X - hull.Average(h => h.X))).ToList();
-
- var step = sorted.Count / 4;
- return new[]
- {
- sorted[0],
- sorted[step],
- sorted[step * 2],
- sorted[step * 3]
- };
- }
-
- ///
- /// Calculate cross product of vectors (b-a) and (c-b).
- ///
- private static float CrossProduct(SKPoint a, SKPoint b, SKPoint c)
- {
- return (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);
- }
-
- ///
- /// Calculate distance between two points.
- ///
- private static float Distance(SKPoint a, SKPoint b)
- {
- var dx = b.X - a.X;
- var dy = b.Y - a.Y;
- return MathF.Sqrt(dx * dx + dy * dy);
- }
-
- ///
- /// Calculate the area of a quadrilateral.
- ///
- private static float CalculateQuadArea(SKPoint[] quad)
- {
- // Shoelace formula
- var area = 0f;
- for (var i = 0; i < 4; i++)
- {
- var j = (i + 1) % 4;
- area += quad[i].X * quad[j].Y;
- area -= quad[j].X * quad[i].Y;
- }
- return Math.Abs(area) / 2;
- }
-
- ///
- /// Calculate how well the aspect ratio matches a card.
- ///
- private static float CalculateAspectRatioScore(SKPoint[] quad)
- {
- // Calculate width and height of the quadrilateral
- var width1 = Distance(quad[0], quad[1]);
- var width2 = Distance(quad[2], quad[3]);
- var height1 = Distance(quad[1], quad[2]);
- var height2 = Distance(quad[3], quad[0]);
-
- var avgWidth = (width1 + width2) / 2;
- var avgHeight = (height1 + height2) / 2;
-
- // Ensure height > width (portrait orientation)
- var aspectRatio = avgWidth > avgHeight
- ? avgWidth / avgHeight
- : avgHeight / avgWidth;
-
- var expectedRatio = CardAspectRatio;
- var deviation = Math.Abs(aspectRatio - expectedRatio) / expectedRatio;
-
- // Score from 0 to 1 based on how close to expected ratio
- return Math.Max(0, 1 - deviation / AspectRatioTolerance);
- }
-
- ///
- /// Check if a quadrilateral is convex.
- ///
- private static bool IsConvex(SKPoint[] quad)
- {
- var sign = 0;
-
- for (var i = 0; i < 4; i++)
- {
- var cross = CrossProduct(quad[i], quad[(i + 1) % 4], quad[(i + 2) % 4]);
-
- if (Math.Abs(cross) < 0.0001f) continue;
-
- var currentSign = cross > 0 ? 1 : -1;
-
- if (sign == 0)
- sign = currentSign;
- else if (sign != currentSign)
- return false;
- }
-
- return true;
- }
-
- ///
- /// Order corners consistently: top-left, top-right, bottom-right, bottom-left.
- ///
- private static SKPoint[] OrderCorners(SKPoint[] corners)
- {
- // Find center
- var centerX = corners.Average(c => c.X);
- var centerY = corners.Average(c => c.Y);
-
- // Classify each corner
- var topLeft = corners.Where(c => c.X < centerX && c.Y < centerY).OrderBy(c => c.X + c.Y).FirstOrDefault();
- var topRight = corners.Where(c => c.X >= centerX && c.Y < centerY).OrderBy(c => c.Y - c.X).FirstOrDefault();
- var bottomRight = corners.Where(c => c.X >= centerX && c.Y >= centerY).OrderByDescending(c => c.X + c.Y).FirstOrDefault();
- var bottomLeft = corners.Where(c => c.X < centerX && c.Y >= centerY).OrderByDescending(c => c.Y - c.X).FirstOrDefault();
-
- // Handle edge cases by sorting by angle from center
- if (topLeft == default || topRight == default || bottomRight == default || bottomLeft == default)
- {
- var sorted = corners.OrderBy(c => MathF.Atan2(c.Y - centerY, c.X - centerX)).ToArray();
- // Rotate to start with top-left
- var minSum = sorted.Select((c, i) => (c.X + c.Y, i)).Min().i;
- return sorted.Skip(minSum).Concat(sorted.Take(minSum)).ToArray();
- }
-
- return new[] { topLeft, topRight, bottomRight, bottomLeft };
- }
-
- ///
- /// Calculate confidence of the detection.
- ///
- private static float CalculateConfidence(SKPoint[] corners, float imageArea)
- {
- var area = CalculateQuadArea(corners);
- var areaScore = Math.Min(area / imageArea / 0.5f, 1f); // Prefer larger cards
- var aspectScore = CalculateAspectRatioScore(corners);
-
- return areaScore * 0.4f + aspectScore * 0.6f;
- }
-}
diff --git a/src/Scry.Core/Imaging/ImagePreprocessor.cs b/src/Scry.Core/Imaging/ImagePreprocessor.cs
deleted file mode 100644
index 6eb0429..0000000
--- a/src/Scry.Core/Imaging/ImagePreprocessor.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-using SkiaSharp;
-
-namespace Scry.Core.Imaging;
-
-public static class ImagePreprocessor
-{
- public static SKBitmap ApplyClahe(SKBitmap source, int tileSize = 8, float clipLimit = 2.0f)
- {
- var width = source.Width;
- var height = source.Height;
- var result = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
-
- var labImage = ConvertToLab(source);
-
- var tilesX = (width + tileSize - 1) / tileSize;
- var tilesY = (height + tileSize - 1) / tileSize;
-
- var tileMappings = new byte[tilesY, tilesX, 256];
-
- for (var ty = 0; ty < tilesY; ty++)
- {
- for (var tx = 0; tx < tilesX; tx++)
- {
- var startX = tx * tileSize;
- var startY = ty * tileSize;
- var endX = Math.Min(startX + tileSize, width);
- var endY = Math.Min(startY + tileSize, height);
-
- var histogram = new int[256];
- var pixelCount = 0;
-
- for (var y = startY; y < endY; y++)
- {
- for (var x = startX; x < endX; x++)
- {
- var l = (int)labImage[y, x, 0];
- histogram[l]++;
- pixelCount++;
- }
- }
-
- var clipCount = (int)(clipLimit * pixelCount / 256);
- var excess = 0;
-
- for (var i = 0; i < 256; i++)
- {
- if (histogram[i] > clipCount)
- {
- excess += histogram[i] - clipCount;
- histogram[i] = clipCount;
- }
- }
-
- var redistribution = excess / 256;
- for (var i = 0; i < 256; i++)
- {
- histogram[i] += redistribution;
- }
-
- var cdf = new int[256];
- cdf[0] = histogram[0];
- for (var i = 1; i < 256; i++)
- {
- cdf[i] = cdf[i - 1] + histogram[i];
- }
-
- var cdfMin = cdf.FirstOrDefault(c => c > 0);
- if (cdfMin == 0) cdfMin = 1;
- var denominator = Math.Max(1, pixelCount - cdfMin);
-
- for (var i = 0; i < 256; i++)
- {
- tileMappings[ty, tx, i] = (byte)Math.Clamp((cdf[i] - cdfMin) * 255 / denominator, 0, 255);
- }
- }
- }
-
- for (var y = 0; y < height; y++)
- {
- for (var x = 0; x < width; x++)
- {
- var l = (int)labImage[y, x, 0];
- var a = labImage[y, x, 1];
- var b = labImage[y, x, 2];
-
- var txFloat = (float)x / tileSize - 0.5f;
- var tyFloat = (float)y / tileSize - 0.5f;
-
- var tx1 = Math.Clamp((int)Math.Floor(txFloat), 0, tilesX - 1);
- var ty1 = Math.Clamp((int)Math.Floor(tyFloat), 0, tilesY - 1);
- var tx2 = Math.Clamp(tx1 + 1, 0, tilesX - 1);
- var ty2 = Math.Clamp(ty1 + 1, 0, tilesY - 1);
-
- var xRatio = txFloat - tx1;
- var yRatio = tyFloat - ty1;
- xRatio = Math.Clamp(xRatio, 0, 1);
- yRatio = Math.Clamp(yRatio, 0, 1);
-
- var v1 = tileMappings[ty1, tx1, l];
- var v2 = tileMappings[ty1, tx2, l];
- var v3 = tileMappings[ty2, tx1, l];
- var v4 = tileMappings[ty2, tx2, l];
-
- var top = v1 * (1 - xRatio) + v2 * xRatio;
- var bottom = v3 * (1 - xRatio) + v4 * xRatio;
- var newL = top * (1 - yRatio) + bottom * yRatio;
-
- var (r, g, bl) = LabToRgb(newL, a, b);
- result.SetPixel(x, y, new SKColor(r, g, bl));
- }
- }
-
- return result;
- }
-
- private static float[,,] ConvertToLab(SKBitmap bitmap)
- {
- var result = new float[bitmap.Height, bitmap.Width, 3];
-
- for (var y = 0; y < bitmap.Height; y++)
- {
- for (var x = 0; x < bitmap.Width; x++)
- {
- var pixel = bitmap.GetPixel(x, y);
- var (l, a, b) = RgbToLab(pixel.Red, pixel.Green, pixel.Blue);
- result[y, x, 0] = l;
- result[y, x, 1] = a;
- result[y, x, 2] = b;
- }
- }
-
- return result;
- }
-
- private static (float L, float A, float B) RgbToLab(byte r, byte g, byte b)
- {
- var rf = PivotRgb(r / 255.0);
- var gf = PivotRgb(g / 255.0);
- var bf = PivotRgb(b / 255.0);
-
- var x = rf * 0.4124564 + gf * 0.3575761 + bf * 0.1804375;
- var y = rf * 0.2126729 + gf * 0.7151522 + bf * 0.0721750;
- var z = rf * 0.0193339 + gf * 0.1191920 + bf * 0.9503041;
-
- x /= 0.95047;
- z /= 1.08883;
-
- x = PivotXyz(x);
- y = PivotXyz(y);
- z = PivotXyz(z);
-
- var l = (float)(116 * y - 16);
- var a = (float)(500 * (x - y));
- var bVal = (float)(200 * (y - z));
-
- l = l * 255 / 100;
-
- return (l, a, bVal);
- }
-
- private static (byte R, byte G, byte B) LabToRgb(double l, double a, double b)
- {
- l = l * 100 / 255;
-
- var y = (l + 16) / 116;
- var x = a / 500 + y;
- var z = y - b / 200;
-
- var x3 = x * x * x;
- var y3 = y * y * y;
- var z3 = z * z * z;
-
- x = x3 > 0.008856 ? x3 : (x - 16.0 / 116) / 7.787;
- y = y3 > 0.008856 ? y3 : (y - 16.0 / 116) / 7.787;
- z = z3 > 0.008856 ? z3 : (z - 16.0 / 116) / 7.787;
-
- x *= 0.95047;
- z *= 1.08883;
-
- var rf = x * 3.2404542 + y * -1.5371385 + z * -0.4985314;
- var gf = x * -0.9692660 + y * 1.8760108 + z * 0.0415560;
- var bf = x * 0.0556434 + y * -0.2040259 + z * 1.0572252;
-
- rf = rf > 0.0031308 ? 1.055 * Math.Pow(rf, 1 / 2.4) - 0.055 : 12.92 * rf;
- gf = gf > 0.0031308 ? 1.055 * Math.Pow(gf, 1 / 2.4) - 0.055 : 12.92 * gf;
- bf = bf > 0.0031308 ? 1.055 * Math.Pow(bf, 1 / 2.4) - 0.055 : 12.92 * bf;
-
- return (
- (byte)Math.Clamp(rf * 255, 0, 255),
- (byte)Math.Clamp(gf * 255, 0, 255),
- (byte)Math.Clamp(bf * 255, 0, 255)
- );
- }
-
- private static double PivotRgb(double n)
- {
- return n > 0.04045 ? Math.Pow((n + 0.055) / 1.055, 2.4) : n / 12.92;
- }
-
- private static double PivotXyz(double n)
- {
- return n > 0.008856 ? Math.Pow(n, 1.0 / 3) : 7.787 * n + 16.0 / 116;
- }
-
- public static SKBitmap Resize(SKBitmap source, int width, int height)
- {
- var info = new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- var sampling = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear);
- return source.Resize(info, sampling);
- }
-
- public static SKBitmap ToGrayscale(SKBitmap source)
- {
- var result = new SKBitmap(source.Width, source.Height, SKColorType.Gray8, SKAlphaType.Opaque);
-
- for (var y = 0; y < source.Height; y++)
- {
- for (var x = 0; x < source.Width; x++)
- {
- var pixel = source.GetPixel(x, y);
- var gray = (byte)(0.299 * pixel.Red + 0.587 * pixel.Green + 0.114 * pixel.Blue);
- result.SetPixel(x, y, new SKColor(gray, gray, gray));
- }
- }
-
- return result;
- }
-}
diff --git a/src/Scry.Core/Imaging/PerceptualHash.cs b/src/Scry.Core/Imaging/PerceptualHash.cs
deleted file mode 100644
index 7d90c87..0000000
--- a/src/Scry.Core/Imaging/PerceptualHash.cs
+++ /dev/null
@@ -1,208 +0,0 @@
-using SkiaSharp;
-
-namespace Scry.Core.Imaging;
-
-public static class PerceptualHash
-{
- private const int HashSize = 32;
- private const int DctSize = 32;
-
- public static byte[] ComputeHash(SKBitmap image)
- {
- using var resized = ImagePreprocessor.Resize(image, DctSize, DctSize);
- using var grayscale = ImagePreprocessor.ToGrayscale(resized);
-
- var pixels = new double[DctSize, DctSize];
- for (var y = 0; y < DctSize; y++)
- {
- for (var x = 0; x < DctSize; x++)
- {
- var pixel = grayscale.GetPixel(x, y);
- pixels[y, x] = pixel.Red;
- }
- }
-
- var dct = ComputeDct(pixels);
-
- var lowFreq = new double[8 * 8];
- var idx = 0;
- for (var y = 0; y < 8; y++)
- {
- for (var x = 0; x < 8; x++)
- {
- if (x == 0 && y == 0) continue;
- lowFreq[idx++] = dct[y, x];
- }
- }
-
- var median = GetMedian(lowFreq.Take(63).ToArray());
-
- var hash = new byte[8];
- idx = 0;
- for (var y = 0; y < 8; y++)
- {
- for (var x = 0; x < 8; x++)
- {
- if (x == 0 && y == 0) continue;
- if (dct[y, x] > median)
- {
- hash[idx / 8] |= (byte)(1 << (7 - idx % 8));
- }
- idx++;
- }
- }
-
- return hash;
- }
-
- public static byte[] ComputeColorHash(SKBitmap image)
- {
- using var resized = ImagePreprocessor.Resize(image, DctSize, DctSize);
-
- var redPixels = new double[DctSize, DctSize];
- var greenPixels = new double[DctSize, DctSize];
- var bluePixels = new double[DctSize, DctSize];
-
- for (var y = 0; y < DctSize; y++)
- {
- for (var x = 0; x < DctSize; x++)
- {
- var pixel = resized.GetPixel(x, y);
- redPixels[y, x] = pixel.Red;
- greenPixels[y, x] = pixel.Green;
- bluePixels[y, x] = pixel.Blue;
- }
- }
-
- var redHash = ComputeChannelHash(redPixels);
- var greenHash = ComputeChannelHash(greenPixels);
- var blueHash = ComputeChannelHash(bluePixels);
-
- var combined = new byte[redHash.Length + greenHash.Length + blueHash.Length];
- Array.Copy(redHash, 0, combined, 0, redHash.Length);
- Array.Copy(greenHash, 0, combined, redHash.Length, greenHash.Length);
- Array.Copy(blueHash, 0, combined, redHash.Length + greenHash.Length, blueHash.Length);
-
- return combined;
- }
-
- private static byte[] ComputeChannelHash(double[,] pixels)
- {
- var dct = ComputeDct(pixels);
-
- var lowFreq = new double[63];
- var idx = 0;
- for (var y = 0; y < 8; y++)
- {
- for (var x = 0; x < 8; x++)
- {
- if (x == 0 && y == 0) continue;
- lowFreq[idx++] = dct[y, x];
- }
- }
-
- var median = GetMedian(lowFreq);
-
- var hash = new byte[8];
- idx = 0;
- for (var y = 0; y < 8; y++)
- {
- for (var x = 0; x < 8; x++)
- {
- if (x == 0 && y == 0) continue;
- if (dct[y, x] > median)
- {
- hash[idx / 8] |= (byte)(1 << (7 - idx % 8));
- }
- idx++;
- }
- }
-
- return hash;
- }
-
- private static double[,] ComputeDct(double[,] input)
- {
- var n = input.GetLength(0);
- var output = new double[n, n];
-
- var coefficients = new double[n];
- coefficients[0] = 1.0 / Math.Sqrt(n);
- for (var i = 1; i < n; i++)
- {
- coefficients[i] = Math.Sqrt(2.0 / n);
- }
-
- var cosTable = new double[n, n];
- for (var i = 0; i < n; i++)
- {
- for (var j = 0; j < n; j++)
- {
- cosTable[i, j] = Math.Cos((2 * j + 1) * i * Math.PI / (2 * n));
- }
- }
-
- var temp = new double[n, n];
- for (var u = 0; u < n; u++)
- {
- for (var y = 0; y < n; y++)
- {
- var sum = 0.0;
- for (var x = 0; x < n; x++)
- {
- sum += input[y, x] * cosTable[u, x];
- }
- temp[u, y] = sum * coefficients[u];
- }
- }
-
- for (var u = 0; u < n; u++)
- {
- for (var v = 0; v < n; v++)
- {
- var sum = 0.0;
- for (var y = 0; y < n; y++)
- {
- sum += temp[u, y] * cosTable[v, y];
- }
- output[v, u] = sum * coefficients[v];
- }
- }
-
- return output;
- }
-
- private static double GetMedian(double[] values)
- {
- var sorted = values.OrderBy(v => v).ToArray();
- var mid = sorted.Length / 2;
- return sorted.Length % 2 == 0
- ? (sorted[mid - 1] + sorted[mid]) / 2
- : sorted[mid];
- }
-
- public static int HammingDistance(byte[] hash1, byte[] hash2)
- {
- if (hash1.Length != hash2.Length)
- throw new ArgumentException("Hash lengths must match");
-
- var distance = 0;
- for (var i = 0; i < hash1.Length; i++)
- {
- var xor = hash1[i] ^ hash2[i];
- while (xor != 0)
- {
- distance += xor & 1;
- xor >>= 1;
- }
- }
-
- return distance;
- }
-
- public static float CalculateConfidence(int hammingDistance, int hashBits)
- {
- var similarity = 1.0f - (float)hammingDistance / hashBits;
- return similarity;
- }
-}
diff --git a/src/Scry.Core/Imaging/PerspectiveCorrection.cs b/src/Scry.Core/Imaging/PerspectiveCorrection.cs
deleted file mode 100644
index 7f5f02f..0000000
--- a/src/Scry.Core/Imaging/PerspectiveCorrection.cs
+++ /dev/null
@@ -1,295 +0,0 @@
-using SkiaSharp;
-
-namespace Scry.Core.Imaging;
-
-///
-/// Performs perspective correction to transform a quadrilateral region into a rectangle.
-///
-public static class PerspectiveCorrection
-{
- ///
- /// Standard output size for corrected card images.
- /// Using 480x670 to maintain card aspect ratio (63:88).
- ///
- public const int OutputWidth = 480;
- public const int OutputHeight = 670;
-
- ///
- /// Apply perspective correction to extract and normalize a card from an image.
- ///
- /// Source image containing the card.
- /// Four corners of the card in order: top-left, top-right, bottom-right, bottom-left.
- /// Width of the output image.
- /// Height of the output image.
- /// A new bitmap with the perspective-corrected card.
- public static SKBitmap WarpPerspective(
- SKBitmap source,
- SKPoint[] corners,
- int outputWidth = OutputWidth,
- int outputHeight = OutputHeight)
- {
- if (corners.Length != 4)
- throw new ArgumentException("Exactly 4 corners required", nameof(corners));
-
- // Determine if the card is in landscape orientation (rotated 90°)
- var width1 = Distance(corners[0], corners[1]);
- var height1 = Distance(corners[1], corners[2]);
-
- // If width > height, card is landscape - swap dimensions and reorder corners
- SKPoint[] orderedCorners;
- int targetWidth, targetHeight;
-
- if (width1 > height1)
- {
- // Card is landscape - rotate corners to portrait
- orderedCorners = new[] { corners[1], corners[2], corners[3], corners[0] };
- targetWidth = outputWidth;
- targetHeight = outputHeight;
- }
- else
- {
- orderedCorners = corners;
- targetWidth = outputWidth;
- targetHeight = outputHeight;
- }
-
- // Compute the perspective transform matrix
- var matrix = ComputePerspectiveTransform(orderedCorners, targetWidth, targetHeight);
-
- // Apply the transform
- var result = new SKBitmap(targetWidth, targetHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
-
- for (var y = 0; y < targetHeight; y++)
- {
- for (var x = 0; x < targetWidth; x++)
- {
- // Apply inverse transform to find source coordinates
- var srcPoint = ApplyInverseTransform(matrix, x, y);
-
- // Bilinear interpolation for smooth sampling
- var color = SampleBilinear(source, srcPoint.X, srcPoint.Y);
- result.SetPixel(x, y, color);
- }
- }
-
- return result;
- }
-
- ///
- /// Compute a perspective transform matrix from quad corners to rectangle.
- /// Uses the Direct Linear Transform (DLT) algorithm.
- ///
- private static float[] ComputePerspectiveTransform(SKPoint[] src, int dstWidth, int dstHeight)
- {
- // Destination corners (rectangle)
- var dst = new SKPoint[]
- {
- new(0, 0),
- new(dstWidth - 1, 0),
- new(dstWidth - 1, dstHeight - 1),
- new(0, dstHeight - 1)
- };
-
- // Build the 8x8 matrix for solving the homography
- // We're solving for the 8 parameters of the 3x3 perspective matrix (h33 = 1)
- var A = new double[8, 8];
- var b = new double[8];
-
- for (var i = 0; i < 4; i++)
- {
- var sx = src[i].X;
- var sy = src[i].Y;
- var dx = dst[i].X;
- var dy = dst[i].Y;
-
- A[i * 2, 0] = sx;
- A[i * 2, 1] = sy;
- A[i * 2, 2] = 1;
- A[i * 2, 3] = 0;
- A[i * 2, 4] = 0;
- A[i * 2, 5] = 0;
- A[i * 2, 6] = -dx * sx;
- A[i * 2, 7] = -dx * sy;
- b[i * 2] = dx;
-
- A[i * 2 + 1, 0] = 0;
- A[i * 2 + 1, 1] = 0;
- A[i * 2 + 1, 2] = 0;
- A[i * 2 + 1, 3] = sx;
- A[i * 2 + 1, 4] = sy;
- A[i * 2 + 1, 5] = 1;
- A[i * 2 + 1, 6] = -dy * sx;
- A[i * 2 + 1, 7] = -dy * sy;
- b[i * 2 + 1] = dy;
- }
-
- // Solve using Gaussian elimination
- var h = SolveLinearSystem(A, b);
-
- // Return the 3x3 matrix as a flat array [h11, h12, h13, h21, h22, h23, h31, h32, h33]
- return new float[]
- {
- (float)h[0], (float)h[1], (float)h[2],
- (float)h[3], (float)h[4], (float)h[5],
- (float)h[6], (float)h[7], 1f
- };
- }
-
- ///
- /// Solve a linear system Ax = b using Gaussian elimination with partial pivoting.
- ///
- private static double[] SolveLinearSystem(double[,] A, double[] b)
- {
- var n = b.Length;
- var augmented = new double[n, n + 1];
-
- // Create augmented matrix
- for (var i = 0; i < n; i++)
- {
- for (var j = 0; j < n; j++)
- augmented[i, j] = A[i, j];
- augmented[i, n] = b[i];
- }
-
- // Forward elimination with partial pivoting
- for (var col = 0; col < n; col++)
- {
- // Find pivot
- var maxRow = col;
- for (var row = col + 1; row < n; row++)
- {
- if (Math.Abs(augmented[row, col]) > Math.Abs(augmented[maxRow, col]))
- maxRow = row;
- }
-
- // Swap rows
- for (var j = 0; j <= n; j++)
- {
- (augmented[col, j], augmented[maxRow, j]) = (augmented[maxRow, j], augmented[col, j]);
- }
-
- // Eliminate
- for (var row = col + 1; row < n; row++)
- {
- if (Math.Abs(augmented[col, col]) < 1e-10) continue;
-
- var factor = augmented[row, col] / augmented[col, col];
- for (var j = col; j <= n; j++)
- {
- augmented[row, j] -= factor * augmented[col, j];
- }
- }
- }
-
- // Back substitution
- var x = new double[n];
- for (var i = n - 1; i >= 0; i--)
- {
- x[i] = augmented[i, n];
- for (var j = i + 1; j < n; j++)
- {
- x[i] -= augmented[i, j] * x[j];
- }
- if (Math.Abs(augmented[i, i]) > 1e-10)
- x[i] /= augmented[i, i];
- }
-
- return x;
- }
-
- ///
- /// Apply the inverse of the perspective transform to map destination to source coordinates.
- ///
- private static SKPoint ApplyInverseTransform(float[] H, float x, float y)
- {
- // H maps src -> dst, we need dst -> src
- // Compute inverse of H
- var inv = InvertMatrix3x3(H);
-
- // Apply inverse transform
- var w = inv[6] * x + inv[7] * y + inv[8];
- if (Math.Abs(w) < 1e-10) w = 1e-10f;
-
- var srcX = (inv[0] * x + inv[1] * y + inv[2]) / w;
- var srcY = (inv[3] * x + inv[4] * y + inv[5]) / w;
-
- return new SKPoint(srcX, srcY);
- }
-
- ///
- /// Invert a 3x3 matrix.
- ///
- private static float[] InvertMatrix3x3(float[] m)
- {
- var det = m[0] * (m[4] * m[8] - m[5] * m[7])
- - m[1] * (m[3] * m[8] - m[5] * m[6])
- + m[2] * (m[3] * m[7] - m[4] * m[6]);
-
- if (Math.Abs(det) < 1e-10f)
- det = 1e-10f;
-
- var invDet = 1f / det;
-
- return new float[]
- {
- (m[4] * m[8] - m[5] * m[7]) * invDet,
- (m[2] * m[7] - m[1] * m[8]) * invDet,
- (m[1] * m[5] - m[2] * m[4]) * invDet,
- (m[5] * m[6] - m[3] * m[8]) * invDet,
- (m[0] * m[8] - m[2] * m[6]) * invDet,
- (m[2] * m[3] - m[0] * m[5]) * invDet,
- (m[3] * m[7] - m[4] * m[6]) * invDet,
- (m[1] * m[6] - m[0] * m[7]) * invDet,
- (m[0] * m[4] - m[1] * m[3]) * invDet
- };
- }
-
- ///
- /// Sample a pixel using bilinear interpolation.
- ///
- private static SKColor SampleBilinear(SKBitmap source, float x, float y)
- {
- // Clamp to valid range
- x = Math.Clamp(x, 0, source.Width - 1);
- y = Math.Clamp(y, 0, source.Height - 1);
-
- var x0 = (int)Math.Floor(x);
- var y0 = (int)Math.Floor(y);
- var x1 = Math.Min(x0 + 1, source.Width - 1);
- var y1 = Math.Min(y0 + 1, source.Height - 1);
-
- var xFrac = x - x0;
- var yFrac = y - y0;
-
- var c00 = source.GetPixel(x0, y0);
- var c10 = source.GetPixel(x1, y0);
- var c01 = source.GetPixel(x0, y1);
- var c11 = source.GetPixel(x1, y1);
-
- // Interpolate
- var r = Lerp(Lerp(c00.Red, c10.Red, xFrac), Lerp(c01.Red, c11.Red, xFrac), yFrac);
- var g = Lerp(Lerp(c00.Green, c10.Green, xFrac), Lerp(c01.Green, c11.Green, xFrac), yFrac);
- var b = Lerp(Lerp(c00.Blue, c10.Blue, xFrac), Lerp(c01.Blue, c11.Blue, xFrac), yFrac);
- var a = Lerp(Lerp(c00.Alpha, c10.Alpha, xFrac), Lerp(c01.Alpha, c11.Alpha, xFrac), yFrac);
-
- return new SKColor((byte)r, (byte)g, (byte)b, (byte)a);
- }
-
- ///
- /// Linear interpolation.
- ///
- private static float Lerp(float a, float b, float t)
- {
- return a + (b - a) * t;
- }
-
- ///
- /// Calculate distance between two points.
- ///
- private static float Distance(SKPoint a, SKPoint b)
- {
- var dx = b.X - a.X;
- var dy = b.Y - a.Y;
- return MathF.Sqrt(dx * dx + dy * dy);
- }
-}
diff --git a/src/Scry.Core/Models/Card.cs b/src/Scry.Core/Models/Card.cs
deleted file mode 100644
index 50d3bbd..0000000
--- a/src/Scry.Core/Models/Card.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-namespace Scry.Core.Models;
-
-///
-/// Represents a specific printing of a card in a set.
-/// Maps to Scryfall's Card object (which is really a printing).
-/// Contains the perceptual hash for image matching.
-/// Includes denormalized Oracle data for convenience.
-///
-public record Card
-{
- ///
- /// Scryfall's unique card ID for this specific printing.
- ///
- public required string Id { get; init; }
-
- ///
- /// Oracle ID linking to the abstract game card.
- ///
- public required string OracleId { get; init; }
-
- ///
- /// Set ID this printing belongs to.
- ///
- public required string SetId { get; init; }
-
- ///
- /// Set code (e.g., "lea", "mh2") - denormalized for convenience.
- ///
- public string? SetCode { get; init; }
-
- ///
- /// Set name - denormalized for convenience.
- ///
- public string? SetName { get; init; }
-
- ///
- /// Card name - denormalized from Oracle for convenience.
- ///
- public required string Name { get; init; }
-
- ///
- /// Collector number within the set.
- ///
- public string? CollectorNumber { get; init; }
-
- ///
- /// Rarity (common, uncommon, rare, mythic).
- ///
- public string? Rarity { get; init; }
-
- ///
- /// Artist name.
- ///
- public string? Artist { get; init; }
-
- ///
- /// Illustration ID - same across printings with identical art.
- ///
- public string? IllustrationId { get; init; }
-
- ///
- /// URI to the card image (normal size).
- ///
- public string? ImageUri { get; init; }
-
- ///
- /// Perceptual hash for image matching.
- ///
- public byte[]? Hash { get; init; }
-
- ///
- /// Language code (e.g., "en", "ja").
- ///
- public string? Lang { get; init; }
-
- ///
- /// USD price for non-foil.
- ///
- public decimal? PricesUsd { get; init; }
-
- ///
- /// USD price for foil.
- ///
- public decimal? PricesUsdFoil { get; init; }
-
- #region Denormalized Oracle Fields (for App layer convenience)
-
- ///
- /// Mana cost in Scryfall notation (e.g., "{2}{U}{U}").
- /// Denormalized from Oracle.
- ///
- public string? ManaCost { get; init; }
-
- ///
- /// Full type line (e.g., "Legendary Creature — Human Wizard").
- /// Denormalized from Oracle.
- ///
- public string? TypeLine { get; init; }
-
- ///
- /// Official Oracle rules text.
- /// Denormalized from Oracle.
- ///
- public string? OracleText { get; init; }
-
- ///
- /// Power for creatures (may contain non-numeric values like "*").
- /// Denormalized from Oracle.
- ///
- public string? Power { get; init; }
-
- ///
- /// Toughness for creatures (may contain non-numeric values like "*").
- /// Denormalized from Oracle.
- ///
- public string? Toughness { get; init; }
-
- #endregion
-
- #region Compatibility Aliases
-
- ///
- /// Alias for ImageUri for compatibility.
- ///
- public string? ImageUrl => ImageUri;
-
- ///
- /// Alias for PricesUsd for compatibility.
- ///
- public decimal? Price => PricesUsd;
-
- ///
- /// Alias for Id (Scryfall ID) for compatibility.
- ///
- public string ScryfallId => Id;
-
- ///
- /// Alias for PricesUsd for compatibility.
- ///
- public decimal? PriceUsd => PricesUsd;
-
- #endregion
-}
diff --git a/src/Scry.Core/Models/Oracle.cs b/src/Scry.Core/Models/Oracle.cs
deleted file mode 100644
index 9b76120..0000000
--- a/src/Scry.Core/Models/Oracle.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-namespace Scry.Core.Models;
-
-///
-/// Represents an abstract game card - the rules object shared across all printings.
-/// Maps to Scryfall's oracle_id concept.
-///
-public record Oracle
-{
- ///
- /// Scryfall's oracle_id - unique identifier for this game card across all printings.
- ///
- public required string Id { get; init; }
-
- ///
- /// The card name (e.g., "Lightning Bolt").
- ///
- public required string Name { get; init; }
-
- ///
- /// Mana cost in Scryfall notation (e.g., "{2}{U}{U}").
- ///
- public string? ManaCost { get; init; }
-
- ///
- /// Mana value (converted mana cost).
- ///
- public double? Cmc { get; init; }
-
- ///
- /// Full type line (e.g., "Legendary Creature — Human Wizard").
- ///
- public string? TypeLine { get; init; }
-
- ///
- /// Official Oracle rules text.
- ///
- public string? OracleText { get; init; }
-
- ///
- /// Card colors as JSON array (e.g., ["U", "R"]).
- ///
- public string? Colors { get; init; }
-
- ///
- /// Color identity for Commander as JSON array.
- ///
- public string? ColorIdentity { get; init; }
-
- ///
- /// Keywords as JSON array (e.g., ["Flying", "Trample"]).
- ///
- public string? Keywords { get; init; }
-
- ///
- /// Whether this card is on the Reserved List.
- ///
- public bool Reserved { get; init; }
-
- ///
- /// Format legalities as JSON object.
- ///
- public string? Legalities { get; init; }
-
- ///
- /// Power for creatures (may contain non-numeric values like "*").
- ///
- public string? Power { get; init; }
-
- ///
- /// Toughness for creatures (may contain non-numeric values like "*").
- ///
- public string? Toughness { get; init; }
-}
diff --git a/src/Scry.Core/Models/ScanResult.cs b/src/Scry.Core/Models/ScanResult.cs
deleted file mode 100644
index 61feb0b..0000000
--- a/src/Scry.Core/Models/ScanResult.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace Scry.Core.Models;
-
-public record ScanResult
-{
- public bool Success { get; init; }
- public Card? Card { get; init; }
- public float Confidence { get; init; }
- public string? ErrorMessage { get; init; }
- public int HammingDistance { get; init; }
- public TimeSpan ProcessingTime { get; init; }
-
- public static ScanResult Failed(string message) => new()
- {
- Success = false,
- ErrorMessage = message,
- Confidence = 0
- };
-
- public static ScanResult Matched(Card card, float confidence, int hammingDistance, TimeSpan processingTime) => new()
- {
- Success = true,
- Card = card,
- Confidence = confidence,
- HammingDistance = hammingDistance,
- ProcessingTime = processingTime
- };
-}
diff --git a/src/Scry.Core/Models/Set.cs b/src/Scry.Core/Models/Set.cs
deleted file mode 100644
index c2f1b19..0000000
--- a/src/Scry.Core/Models/Set.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-namespace Scry.Core.Models;
-
-///
-/// Represents an MTG set. Maps to Scryfall's Set object.
-///
-public record Set
-{
- ///
- /// Scryfall's unique set ID.
- ///
- public required string Id { get; init; }
-
- ///
- /// Unique 3-6 letter set code (e.g., "lea", "mh2").
- ///
- public required string Code { get; init; }
-
- ///
- /// English name of the set (e.g., "Limited Edition Alpha").
- ///
- public required string Name { get; init; }
-
- ///
- /// Set classification (e.g., "expansion", "core", "masters", "commander").
- ///
- public string? SetType { get; init; }
-
- ///
- /// Release date in ISO 8601 format.
- ///
- public string? ReleasedAt { get; init; }
-
- ///
- /// Number of cards in the set.
- ///
- public int? CardCount { get; init; }
-
- ///
- /// URI to the set's icon SVG.
- ///
- public string? IconSvgUri { get; init; }
-
- ///
- /// Whether this is a digital-only set.
- ///
- public bool Digital { get; init; }
-
- ///
- /// Parent set code for promo/token sets.
- ///
- public string? ParentSetCode { get; init; }
-
- ///
- /// Block name, if applicable.
- ///
- public string? Block { get; init; }
-}
diff --git a/src/Scry.Core/Recognition/CardRecognitionService.cs b/src/Scry.Core/Recognition/CardRecognitionService.cs
deleted file mode 100644
index b560e14..0000000
--- a/src/Scry.Core/Recognition/CardRecognitionService.cs
+++ /dev/null
@@ -1,429 +0,0 @@
-using System.Diagnostics;
-using Microsoft.Extensions.Options;
-using Scry.Core.Data;
-using Scry.Core.Imaging;
-using Scry.Core.Models;
-using SkiaSharp;
-
-namespace Scry.Core.Recognition;
-
-public class CardRecognitionService : IDisposable
-{
- private readonly CardDatabase _database;
- private readonly RecognitionOptions _options;
- private List? _cardCache;
- private readonly SemaphoreSlim _cacheLock = new(1, 1);
-
- private const int ColorHashBits = 192;
- private const int MatchThreshold = 25;
- private const float MinConfidence = 0.85f;
-
- public CardRecognitionService(CardDatabase database, IOptions options)
- {
- _database = database;
- _options = options.Value;
- }
-
- public CardRecognitionService(CardDatabase database) : this(database, Options.Create(new RecognitionOptions()))
- {
- }
-
- public async Task RecognizeAsync(Stream imageStream, CancellationToken ct = default)
- {
- var stopwatch = Stopwatch.StartNew();
-
- try
- {
- using var bitmap = SKBitmap.Decode(imageStream);
- if (bitmap == null)
- {
- return ScanResult.Failed("Could not decode image");
- }
-
- return await RecognizeAsync(bitmap, ct);
- }
- catch (Exception ex)
- {
- return ScanResult.Failed($"Recognition error: {ex.Message}");
- }
- finally
- {
- stopwatch.Stop();
- }
- }
-
- public async Task RecognizeAsync(SKBitmap bitmap, CancellationToken ct = default)
- {
- var stopwatch = Stopwatch.StartNew();
- var debugDir = _options.DebugOutputDirectory;
- var debugEnabled = !string.IsNullOrEmpty(debugDir);
-
- if (debugEnabled)
- {
- Directory.CreateDirectory(debugDir!);
- SaveDebugImage(bitmap, debugDir!, "01_input");
- }
-
- try
- {
- var cards = await GetCardCacheAsync(ct);
- Console.WriteLine($"[Scry] Database has {cards.Count} cards with hashes");
-
- if (cards.Count == 0)
- {
- return ScanResult.Failed("No cards in database. Run sync first.");
- }
-
- // Step 1: Detect and extract card from image (if enabled)
- SKBitmap cardImage;
- bool cardDetected = false;
-
- if (_options.EnableCardDetection)
- {
- var detection = CardDetector.DetectCard(bitmap);
-
- if (debugEnabled)
- {
- // Save detection visualization
- SaveDetectionDebugImage(bitmap, detection, debugDir!);
- }
-
- if (detection.Found)
- {
- cardImage = PerspectiveCorrection.WarpPerspective(bitmap, detection.Corners);
- cardDetected = true;
- Console.WriteLine($"[Scry] Card detected with confidence {detection.Confidence:P0}");
-
- if (debugEnabled)
- {
- SaveDebugImage(cardImage, debugDir!, "03_perspective_corrected");
- }
- }
- else
- {
- // Fall back to using the whole image
- Console.WriteLine($"[Scry] Card detection failed: {detection.DebugMessage}, using full image");
- cardImage = bitmap;
- }
- }
- else
- {
- cardImage = bitmap;
- }
-
- try
- {
- // Step 2: Try matching with rotation variants (if enabled)
- var bestMatch = _options.EnableRotationMatching
- ? await FindBestMatchWithRotationsAsync(cardImage, cards, debugDir, ct)
- : FindBestMatchSingle(cardImage, cards, debugDir);
-
- stopwatch.Stop();
-
- if (bestMatch == null)
- {
- return ScanResult.Failed($"No match found (detection={cardDetected})");
- }
-
- var (matchedCard, distance, rotation) = bestMatch.Value;
- var confidence = PerceptualHash.CalculateConfidence(distance, ColorHashBits);
- Console.WriteLine($"[Scry] Best match: {matchedCard.Name}, distance={distance}, confidence={confidence:P0}, rotation={rotation}°");
-
- if (confidence < MinConfidence)
- {
- return ScanResult.Failed($"Match confidence too low: {confidence:P0}");
- }
-
- return ScanResult.Matched(matchedCard, confidence, distance, stopwatch.Elapsed);
- }
- finally
- {
- // Dispose card image only if we created a new one
- if (cardDetected && cardImage != bitmap)
- {
- cardImage.Dispose();
- }
- }
- }
- catch (Exception ex)
- {
- stopwatch.Stop();
- return ScanResult.Failed($"Recognition error: {ex.Message}");
- }
- }
-
- ///
- /// Compute hash for a card image with the full preprocessing pipeline.
- ///
- public byte[] ComputeHash(SKBitmap bitmap, bool applyPreprocessing = true)
- {
- if (applyPreprocessing)
- {
- // CLAHE is applied as the last step before hashing
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- return PerceptualHash.ComputeColorHash(preprocessed);
- }
-
- return PerceptualHash.ComputeColorHash(bitmap);
- }
-
- ///
- /// Compute hash for a reference image (used when building the database).
- /// Reference images are assumed to be already cropped and oriented correctly.
- ///
- public byte[] ComputeReferenceHash(SKBitmap bitmap)
- {
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- return PerceptualHash.ComputeColorHash(preprocessed);
- }
-
- public async Task InvalidateCacheAsync()
- {
- await _cacheLock.WaitAsync();
- try
- {
- _cardCache = null;
- }
- finally
- {
- _cacheLock.Release();
- }
- }
-
- private async Task> GetCardCacheAsync(CancellationToken ct)
- {
- if (_cardCache != null)
- return _cardCache;
-
- await _cacheLock.WaitAsync(ct);
- try
- {
- _cardCache ??= await _database.GetCardsWithHashAsync(ct);
- return _cardCache;
- }
- finally
- {
- _cacheLock.Release();
- }
- }
-
- ///
- /// Find best match trying all 4 rotations (0°, 90°, 180°, 270°).
- ///
- private Task<(Card Card, int Distance, int Rotation)?> FindBestMatchWithRotationsAsync(
- SKBitmap cardImage,
- List candidates,
- string? debugDir,
- CancellationToken ct)
- {
- return Task.Run(() =>
- {
- Card? bestMatch = null;
- var bestDistance = int.MaxValue;
- var bestRotation = 0;
- var debugEnabled = !string.IsNullOrEmpty(debugDir);
-
- var rotations = new[] { 0, 90, 180, 270 };
-
- foreach (var rotation in rotations)
- {
- ct.ThrowIfCancellationRequested();
-
- using var rotated = rotation == 0 ? null : RotateImage(cardImage, rotation);
- var imageToHash = rotated ?? cardImage;
-
- if (debugEnabled && rotation != 0)
- {
- SaveDebugImage(imageToHash, debugDir!, $"04_rotated_{rotation}");
- }
-
- // Apply CLAHE and compute hash
- using var preprocessed = ImagePreprocessor.ApplyClahe(imageToHash);
-
- if (debugEnabled)
- {
- SaveDebugImage(preprocessed, debugDir!, $"05_clahe_{rotation}");
- }
-
- var queryHash = PerceptualHash.ComputeColorHash(preprocessed);
-
- // Find best match for this rotation
- foreach (var candidate in candidates)
- {
- if (candidate.Hash == null || candidate.Hash.Length != queryHash.Length)
- continue;
-
- var distance = PerceptualHash.HammingDistance(queryHash, candidate.Hash);
-
- if (distance < bestDistance)
- {
- bestDistance = distance;
- bestMatch = candidate;
- bestRotation = rotation;
- }
-
- // Early exit on perfect match
- if (distance == 0 && bestMatch != null)
- return (bestMatch, bestDistance, bestRotation);
- }
- }
-
- if (bestMatch == null || bestDistance > MatchThreshold)
- return null;
-
- return ((Card Card, int Distance, int Rotation)?)(bestMatch, bestDistance, bestRotation);
- }, ct);
- }
-
- ///
- /// Find best match without rotation (single orientation).
- ///
- private (Card Card, int Distance, int Rotation)? FindBestMatchSingle(
- SKBitmap cardImage,
- List candidates,
- string? debugDir)
- {
- var debugEnabled = !string.IsNullOrEmpty(debugDir);
-
- // Apply CLAHE and compute hash
- using var preprocessed = ImagePreprocessor.ApplyClahe(cardImage);
-
- if (debugEnabled)
- {
- SaveDebugImage(preprocessed, debugDir!, "05_clahe_0");
- }
-
- var queryHash = PerceptualHash.ComputeColorHash(preprocessed);
-
- Card? bestMatch = null;
- var bestDistance = int.MaxValue;
-
- foreach (var candidate in candidates)
- {
- if (candidate.Hash == null || candidate.Hash.Length != queryHash.Length)
- continue;
-
- var distance = PerceptualHash.HammingDistance(queryHash, candidate.Hash);
-
- if (distance < bestDistance)
- {
- bestDistance = distance;
- bestMatch = candidate;
- }
-
- if (distance == 0)
- break;
- }
-
- if (bestMatch == null || bestDistance > MatchThreshold)
- return null;
-
- return (bestMatch, bestDistance, 0);
- }
-
- ///
- /// Rotate an image by the specified degrees (90, 180, or 270).
- ///
- private static SKBitmap RotateImage(SKBitmap source, int degrees)
- {
- var (newWidth, newHeight) = degrees switch
- {
- 90 or 270 => (source.Height, source.Width),
- _ => (source.Width, source.Height)
- };
-
- var rotated = new SKBitmap(newWidth, newHeight, source.ColorType, source.AlphaType);
-
- using var canvas = new SKCanvas(rotated);
- canvas.Clear(SKColors.Black);
-
- canvas.Translate(newWidth / 2f, newHeight / 2f);
- canvas.RotateDegrees(degrees);
- canvas.Translate(-source.Width / 2f, -source.Height / 2f);
- canvas.DrawBitmap(source, 0, 0);
-
- return rotated;
- }
-
- ///
- /// Save a debug image to disk.
- ///
- private static void SaveDebugImage(SKBitmap bitmap, string directory, string name)
- {
- var path = Path.Combine(directory, $"{name}.png");
- using var image = SKImage.FromBitmap(bitmap);
- using var data = image.Encode(SKEncodedImageFormat.Png, 100);
- using var stream = File.OpenWrite(path);
- data.SaveTo(stream);
- Console.WriteLine($"[Scry Debug] Saved: {path}");
- }
-
- ///
- /// Save a debug image showing the card detection result.
- ///
- private static void SaveDetectionDebugImage(SKBitmap original, CardDetector.CardDetectionResult detection, string directory)
- {
- using var annotated = new SKBitmap(original.Width, original.Height, original.ColorType, original.AlphaType);
- using var canvas = new SKCanvas(annotated);
-
- canvas.DrawBitmap(original, 0, 0);
-
- if (detection.Found && detection.Corners.Length == 4)
- {
- // Draw detected corners and edges
- using var cornerPaint = new SKPaint
- {
- Color = SKColors.Red,
- Style = SKPaintStyle.Fill,
- IsAntialias = true
- };
-
- using var edgePaint = new SKPaint
- {
- Color = SKColors.Lime,
- Style = SKPaintStyle.Stroke,
- StrokeWidth = 3,
- IsAntialias = true
- };
-
- var corners = detection.Corners;
-
- // Draw edges
- for (int i = 0; i < 4; i++)
- {
- var p1 = corners[i];
- var p2 = corners[(i + 1) % 4];
- canvas.DrawLine(p1.X, p1.Y, p2.X, p2.Y, edgePaint);
- }
-
- // Draw corners
- foreach (var corner in corners)
- {
- canvas.DrawCircle(corner.X, corner.Y, 8, cornerPaint);
- }
- }
-
- // Add debug text
- using var textPaint = new SKPaint
- {
- Color = detection.Found ? SKColors.Lime : SKColors.Red,
- IsAntialias = true
- };
- using var font = new SKFont
- {
- Size = 24
- };
-
- var message = detection.Found
- ? $"Detected: {detection.Confidence:P0}"
- : $"Not found: {detection.DebugMessage}";
- canvas.DrawText(message, 10, 30, SKTextAlign.Left, font, textPaint);
-
- SaveDebugImage(annotated, directory, "02_detection");
- }
-
- public void Dispose()
- {
- _cacheLock.Dispose();
- }
-}
diff --git a/src/Scry.Core/Recognition/HashDatabaseSyncService.cs b/src/Scry.Core/Recognition/HashDatabaseSyncService.cs
deleted file mode 100644
index 80402ae..0000000
--- a/src/Scry.Core/Recognition/HashDatabaseSyncService.cs
+++ /dev/null
@@ -1,263 +0,0 @@
-using Scry.Core.Data;
-using Scry.Core.Imaging;
-using Scry.Core.Models;
-using Scry.Core.Scryfall;
-using SkiaSharp;
-
-namespace Scry.Core.Recognition;
-
-public class HashDatabaseSyncService
-{
- private readonly ScryfallClient _scryfallClient;
- private readonly CardDatabase _database;
- private readonly HttpClient _imageClient;
-
- public event Action? OnProgress;
-
- public HashDatabaseSyncService(ScryfallClient scryfallClient, CardDatabase database, HttpClient? imageClient = null)
- {
- _scryfallClient = scryfallClient;
- _database = database;
- _imageClient = imageClient ?? new HttpClient();
- }
-
- public async Task SyncAsync(SyncOptions? options = null, CancellationToken ct = default)
- {
- options ??= new SyncOptions();
- var result = new SyncResult();
- var startTime = DateTime.UtcNow;
-
- try
- {
- // Fetch all sets first
- ReportProgress(new SyncProgress { Stage = SyncStage.Initializing, Message = "Fetching sets..." });
- var scryfallSets = await _scryfallClient.GetAllSetsAsync(ct);
- var existingSetIds = await _database.GetExistingSetIdsAsync(ct);
-
- var newSets = scryfallSets
- .Where(s => s.Id != null && !existingSetIds.Contains(s.Id))
- .Select(s => s.ToSet())
- .ToList();
-
- if (newSets.Count > 0)
- {
- await _database.InsertSetBatchAsync(newSets, ct);
- }
-
- var bulkInfo = await _scryfallClient.GetBulkDataInfoAsync(options.BulkDataType, ct);
- if (bulkInfo?.DownloadUri == null)
- {
- result.Error = "Could not get bulk data info from Scryfall";
- return result;
- }
-
- var lastSync = await _database.GetMetadataAsync("last_sync_date", ct);
- if (!options.ForceFullSync && lastSync != null)
- {
- if (DateTime.TryParse(lastSync, out var lastSyncDate) &&
- bulkInfo.UpdatedAt <= lastSyncDate)
- {
- result.Skipped = true;
- result.Message = "Database is up to date";
- return result;
- }
- }
-
- ReportProgress(new SyncProgress { Stage = SyncStage.Downloading, Message = "Downloading card data..." });
-
- var existingOracleIds = await _database.GetExistingOracleIdsAsync(ct);
- var cardBatch = new List();
- var oracleBatch = new Dictionary();
- var processed = 0;
- var errors = 0;
-
- await foreach (var scryfallCard in _scryfallClient.StreamBulkDataAsync(bulkInfo.DownloadUri, ct))
- {
- ct.ThrowIfCancellationRequested();
-
- if (scryfallCard.Lang != "en" && !options.IncludeNonEnglish)
- continue;
-
- var imageUri = scryfallCard.GetImageUri(options.ImageSize);
- if (string.IsNullOrEmpty(imageUri))
- continue;
-
- var oracleId = scryfallCard.OracleId ?? scryfallCard.Id ?? "";
-
- try
- {
- var imageBytes = await DownloadWithRetryAsync(imageUri, options.MaxRetries, ct);
- if (imageBytes == null)
- {
- errors++;
- continue;
- }
-
- using var bitmap = SKBitmap.Decode(imageBytes);
- if (bitmap == null)
- {
- errors++;
- continue;
- }
-
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- var hash = PerceptualHash.ComputeColorHash(preprocessed);
-
- // Track oracle if new
- if (!existingOracleIds.Contains(oracleId) && !oracleBatch.ContainsKey(oracleId))
- {
- oracleBatch[oracleId] = scryfallCard.ToOracle();
- }
-
- // Create card with hash
- var card = scryfallCard.ToCard() with { Hash = hash };
- cardBatch.Add(card);
-
- processed++;
-
- if (cardBatch.Count >= options.BatchSize)
- {
- // Insert oracles first
- if (oracleBatch.Count > 0)
- {
- await _database.InsertOracleBatchAsync(oracleBatch.Values, ct);
- foreach (var id in oracleBatch.Keys)
- {
- existingOracleIds.Add(id);
- }
- oracleBatch.Clear();
- }
-
- await _database.InsertCardBatchAsync(cardBatch, ct);
- result.ProcessedCards += cardBatch.Count;
- cardBatch.Clear();
-
- ReportProgress(new SyncProgress
- {
- Stage = SyncStage.Processing,
- ProcessedCards = result.ProcessedCards,
- Message = $"Processed {result.ProcessedCards} cards..."
- });
- }
-
- if (options.RateLimitMs > 0)
- {
- await Task.Delay(options.RateLimitMs, ct);
- }
- }
- catch (Exception ex)
- {
- errors++;
- if (options.StopOnError)
- throw;
-
- result.Errors.Add($"{scryfallCard.Name}: {ex.Message}");
- }
- }
-
- // Insert remaining batches
- if (oracleBatch.Count > 0)
- {
- await _database.InsertOracleBatchAsync(oracleBatch.Values, ct);
- }
-
- if (cardBatch.Count > 0)
- {
- await _database.InsertCardBatchAsync(cardBatch, ct);
- result.ProcessedCards += cardBatch.Count;
- }
-
- await _database.SetMetadataAsync("last_sync_date", DateTime.UtcNow.ToString("O"), ct);
- await _database.SetMetadataAsync("bulk_data_updated", bulkInfo.UpdatedAt?.ToString("O") ?? "", ct);
-
- result.Success = true;
- result.Duration = DateTime.UtcNow - startTime;
- result.ErrorCount = errors;
- result.Message = $"Synced {result.ProcessedCards} cards in {result.Duration.TotalSeconds:F1}s";
-
- ReportProgress(new SyncProgress
- {
- Stage = SyncStage.Complete,
- ProcessedCards = result.ProcessedCards,
- Message = result.Message
- });
- }
- catch (OperationCanceledException)
- {
- result.Cancelled = true;
- result.Message = "Sync cancelled";
- }
- catch (Exception ex)
- {
- result.Error = ex.Message;
- }
-
- return result;
- }
-
- private async Task DownloadWithRetryAsync(string url, int maxRetries, CancellationToken ct)
- {
- for (var attempt = 0; attempt <= maxRetries; attempt++)
- {
- try
- {
- return await _imageClient.GetByteArrayAsync(url, ct);
- }
- catch (HttpRequestException) when (attempt < maxRetries)
- {
- await Task.Delay(1000 * (attempt + 1), ct);
- }
- }
-
- return null;
- }
-
- private void ReportProgress(SyncProgress progress)
- {
- OnProgress?.Invoke(progress);
- }
-}
-
-public class SyncOptions
-{
- public string BulkDataType { get; set; } = "unique_artwork";
- public string ImageSize { get; set; } = "normal";
- public int BatchSize { get; set; } = 100;
- public int RateLimitMs { get; set; } = 50;
- public int MaxRetries { get; set; } = 3;
- public bool ForceFullSync { get; set; }
- public bool IncludeNonEnglish { get; set; }
- public bool StopOnError { get; set; }
-}
-
-public class SyncResult
-{
- public bool Success { get; set; }
- public bool Skipped { get; set; }
- public bool Cancelled { get; set; }
- public int ProcessedCards { get; set; }
- public int ErrorCount { get; set; }
- public TimeSpan Duration { get; set; }
- public string? Message { get; set; }
- public string? Error { get; set; }
- public List Errors { get; set; } = new();
-}
-
-public class SyncProgress
-{
- public SyncStage Stage { get; set; }
- public int ProcessedCards { get; set; }
- public int TotalCards { get; set; }
- public string? Message { get; set; }
-
- public float Percentage => TotalCards > 0 ? (float)ProcessedCards / TotalCards * 100 : 0;
-}
-
-public enum SyncStage
-{
- Initializing,
- Downloading,
- Processing,
- Complete,
- Error
-}
diff --git a/src/Scry.Core/Recognition/RecognitionOptions.cs b/src/Scry.Core/Recognition/RecognitionOptions.cs
deleted file mode 100644
index 8a9f4ee..0000000
--- a/src/Scry.Core/Recognition/RecognitionOptions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Scry.Core.Recognition;
-
-///
-/// Configuration options for card recognition.
-///
-public class RecognitionOptions
-{
- ///
- /// When set, saves debug images of each pipeline step to this directory.
- ///
- public string? DebugOutputDirectory { get; set; }
-
- ///
- /// Enable card detection and perspective correction.
- /// When disabled, assumes the input image is already a cropped card.
- ///
- public bool EnableCardDetection { get; set; } = true;
-
- ///
- /// Try multiple rotations (0°, 90°, 180°, 270°) when matching.
- /// Useful when card orientation is unknown.
- ///
- public bool EnableRotationMatching { get; set; } = true;
-}
diff --git a/src/Scry.Core/Scry.Core.csproj b/src/Scry.Core/Scry.Core.csproj
deleted file mode 100644
index 9464c4d..0000000
--- a/src/Scry.Core/Scry.Core.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- net10.0
- enable
- enable
- Scry.Core
-
-
-
-
-
-
-
-
-
diff --git a/src/Scry.Core/Scryfall/ScryfallClient.cs b/src/Scry.Core/Scryfall/ScryfallClient.cs
deleted file mode 100644
index 58cf64c..0000000
--- a/src/Scry.Core/Scryfall/ScryfallClient.cs
+++ /dev/null
@@ -1,302 +0,0 @@
-using System.IO.Compression;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using Scry.Core.Models;
-
-namespace Scry.Core.Scryfall;
-
-public class ScryfallClient : IDisposable
-{
- private readonly HttpClient _httpClient;
- private const string BulkDataUrl = "https://api.scryfall.com/bulk-data";
- private const string SetsUrl = "https://api.scryfall.com/sets";
-
- public ScryfallClient(HttpClient? httpClient = null)
- {
- _httpClient = httpClient ?? new HttpClient();
- _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Scry/1.0 (MTG Card Scanner)");
- }
-
- public async Task GetBulkDataInfoAsync(string type = "unique_artwork", CancellationToken ct = default)
- {
- var response = await _httpClient.GetStringAsync(BulkDataUrl, ct);
- var bulkData = JsonSerializer.Deserialize(response, JsonOptions);
-
- return bulkData?.Data?.FirstOrDefault(d =>
- d.Type?.Equals(type, StringComparison.OrdinalIgnoreCase) == true);
- }
-
- public async Task> GetAllSetsAsync(CancellationToken ct = default)
- {
- var sets = new List();
- var url = SetsUrl;
-
- while (!string.IsNullOrEmpty(url))
- {
- var response = await _httpClient.GetStringAsync(url, ct);
- var setsResponse = JsonSerializer.Deserialize(response, JsonOptions);
-
- if (setsResponse?.Data != null)
- {
- sets.AddRange(setsResponse.Data);
- }
-
- url = setsResponse?.NextPage;
- }
-
- return sets;
- }
-
- public async IAsyncEnumerable StreamBulkDataAsync(
- string downloadUri,
- [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct = default)
- {
- using var response = await _httpClient.GetAsync(downloadUri, HttpCompletionOption.ResponseHeadersRead, ct);
- response.EnsureSuccessStatusCode();
-
- await using var stream = await response.Content.ReadAsStreamAsync(ct);
-
- Stream dataStream = stream;
- if (downloadUri.EndsWith(".gz", StringComparison.OrdinalIgnoreCase))
- {
- dataStream = new GZipStream(stream, CompressionMode.Decompress);
- }
-
- await foreach (var card in JsonSerializer.DeserializeAsyncEnumerable(dataStream, JsonOptions, ct))
- {
- if (card != null)
- {
- yield return card;
- }
- }
- }
-
- public async Task DownloadImageAsync(string imageUri, CancellationToken ct = default)
- {
- try
- {
- return await _httpClient.GetByteArrayAsync(imageUri, ct);
- }
- catch (HttpRequestException)
- {
- return null;
- }
- }
-
- public void Dispose()
- {
- _httpClient.Dispose();
- }
-
- private static readonly JsonSerializerOptions JsonOptions = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- PropertyNameCaseInsensitive = true
- };
-}
-
-#region API Response Models
-
-public record BulkDataResponse
-{
- public List? Data { get; init; }
-}
-
-public record BulkDataInfo
-{
- public string? Id { get; init; }
- public string? Type { get; init; }
- public string? Name { get; init; }
- public string? DownloadUri { get; init; }
- public DateTime? UpdatedAt { get; init; }
- public long? Size { get; init; }
-}
-
-public record SetsResponse
-{
- public List? Data { get; init; }
- public bool HasMore { get; init; }
- public string? NextPage { get; init; }
-}
-
-public record ScryfallSet
-{
- public string? Id { get; init; }
- public string? Code { get; init; }
- public string? Name { get; init; }
- public string? SetType { get; init; }
- public string? ReleasedAt { get; init; }
- public int? CardCount { get; init; }
- public string? IconSvgUri { get; init; }
- public bool Digital { get; init; }
- public string? ParentSetCode { get; init; }
- public string? Block { get; init; }
-}
-
-public record ScryfallCard
-{
- // Core identifiers
- public string? Id { get; init; }
- public string? OracleId { get; init; }
-
- // Oracle/game card fields
- public string? Name { get; init; }
- public string? ManaCost { get; init; }
- public double? Cmc { get; init; }
- public string? TypeLine { get; init; }
- public string? OracleText { get; init; }
- public List? Colors { get; init; }
- public List? ColorIdentity { get; init; }
- public List? Keywords { get; init; }
- public bool Reserved { get; init; }
- public Dictionary? Legalities { get; init; }
- public string? Power { get; init; }
- public string? Toughness { get; init; }
-
- // Printing-specific fields
- public string? Set { get; init; }
- public string? SetId { get; init; }
- public string? SetName { get; init; }
- public string? CollectorNumber { get; init; }
- public string? Rarity { get; init; }
- public string? Artist { get; init; }
- public string? IllustrationId { get; init; }
- public string? Lang { get; init; }
-
- // Images and prices
- public ImageUris? ImageUris { get; init; }
- public Prices? Prices { get; init; }
-
- // Multi-face cards
- public List? CardFaces { get; init; }
-}
-
-public record ImageUris
-{
- public string? Small { get; init; }
- public string? Normal { get; init; }
- public string? Large { get; init; }
- public string? Png { get; init; }
- public string? ArtCrop { get; init; }
- public string? BorderCrop { get; init; }
-}
-
-public record Prices
-{
- public string? Usd { get; init; }
- public string? UsdFoil { get; init; }
- public string? Eur { get; init; }
-}
-
-public record CardFace
-{
- public string? Name { get; init; }
- public string? ManaCost { get; init; }
- public string? TypeLine { get; init; }
- public string? OracleText { get; init; }
- public List? Colors { get; init; }
- public string? Power { get; init; }
- public string? Toughness { get; init; }
- public ImageUris? ImageUris { get; init; }
-}
-
-#endregion
-
-#region Extension Methods
-
-public static class ScryfallCardExtensions
-{
- ///
- /// Extracts the Oracle (abstract game card) from a Scryfall card.
- ///
- public static Oracle ToOracle(this ScryfallCard scryfall)
- {
- return new Oracle
- {
- Id = scryfall.OracleId ?? scryfall.Id ?? Guid.NewGuid().ToString(),
- Name = scryfall.Name ?? "Unknown",
- ManaCost = scryfall.ManaCost,
- Cmc = scryfall.Cmc,
- TypeLine = scryfall.TypeLine,
- OracleText = scryfall.OracleText,
- Colors = scryfall.Colors != null ? JsonSerializer.Serialize(scryfall.Colors) : null,
- ColorIdentity = scryfall.ColorIdentity != null ? JsonSerializer.Serialize(scryfall.ColorIdentity) : null,
- Keywords = scryfall.Keywords != null ? JsonSerializer.Serialize(scryfall.Keywords) : null,
- Reserved = scryfall.Reserved,
- Legalities = scryfall.Legalities != null ? JsonSerializer.Serialize(scryfall.Legalities) : null,
- Power = scryfall.Power,
- Toughness = scryfall.Toughness,
- };
- }
-
- ///
- /// Converts a Scryfall card to a Card (printing) model.
- /// Note: Hash must be computed separately and set on the returned Card.
- ///
- public static Card ToCard(this ScryfallCard scryfall)
- {
- var imageUris = scryfall.ImageUris ?? scryfall.CardFaces?.FirstOrDefault()?.ImageUris;
-
- return new Card
- {
- Id = scryfall.Id ?? Guid.NewGuid().ToString(),
- OracleId = scryfall.OracleId ?? scryfall.Id ?? Guid.NewGuid().ToString(),
- SetId = scryfall.SetId ?? "",
- SetCode = scryfall.Set,
- SetName = scryfall.SetName,
- Name = scryfall.Name ?? "Unknown",
- CollectorNumber = scryfall.CollectorNumber,
- Rarity = scryfall.Rarity,
- Artist = scryfall.Artist,
- IllustrationId = scryfall.IllustrationId,
- ImageUri = imageUris?.Normal,
- Lang = scryfall.Lang,
- PricesUsd = decimal.TryParse(scryfall.Prices?.Usd, out var usd) ? usd : null,
- PricesUsdFoil = decimal.TryParse(scryfall.Prices?.UsdFoil, out var foil) ? foil : null,
- Hash = null, // Must be computed separately
- // Denormalized Oracle fields
- ManaCost = scryfall.ManaCost,
- TypeLine = scryfall.TypeLine,
- OracleText = scryfall.OracleText,
- Power = scryfall.Power,
- Toughness = scryfall.Toughness,
- };
- }
-
- ///
- /// Converts a Scryfall set to a Set model.
- ///
- public static Set ToSet(this ScryfallSet scryfall)
- {
- return new Set
- {
- Id = scryfall.Id ?? Guid.NewGuid().ToString(),
- Code = scryfall.Code ?? "???",
- Name = scryfall.Name ?? "Unknown",
- SetType = scryfall.SetType,
- ReleasedAt = scryfall.ReleasedAt,
- CardCount = scryfall.CardCount,
- IconSvgUri = scryfall.IconSvgUri,
- Digital = scryfall.Digital,
- ParentSetCode = scryfall.ParentSetCode,
- Block = scryfall.Block,
- };
- }
-
- public static string? GetImageUri(this ScryfallCard card, string size = "normal")
- {
- var uris = card.ImageUris ?? card.CardFaces?.FirstOrDefault()?.ImageUris;
-
- return size.ToLowerInvariant() switch
- {
- "small" => uris?.Small,
- "large" => uris?.Large,
- "png" => uris?.Png,
- "art_crop" => uris?.ArtCrop,
- "border_crop" => uris?.BorderCrop,
- _ => uris?.Normal
- };
- }
-}
-
-#endregion
diff --git a/test/Scry.Tests/CardDatabaseTests.cs b/test/Scry.Tests/CardDatabaseTests.cs
deleted file mode 100644
index 090998e..0000000
--- a/test/Scry.Tests/CardDatabaseTests.cs
+++ /dev/null
@@ -1,304 +0,0 @@
-using Microsoft.Data.Sqlite;
-using Scry.Core.Data;
-using Scry.Core.Models;
-using Xunit;
-
-namespace Scry.Tests;
-
-public class CardDatabaseTests : IDisposable
-{
- private readonly string _dbPath;
- private readonly CardDatabase _database;
-
- public CardDatabaseTests()
- {
- _dbPath = Path.Combine(Path.GetTempPath(), $"scry_test_{Guid.NewGuid()}.db");
- _database = new CardDatabase(_dbPath);
- }
-
- [Fact]
- public async Task InsertCard_ThenRetrieve_ReturnsMatch()
- {
- // First insert oracle and set (foreign keys)
- var oracle = new Oracle
- {
- Id = "oracle-1",
- Name = "Test Card",
- ManaCost = "{1}{U}",
- TypeLine = "Creature"
- };
- await _database.InsertOracleAsync(oracle);
-
- var set = new Set
- {
- Id = "set-1",
- Code = "TST",
- Name = "Test Set"
- };
- await _database.InsertSetAsync(set);
-
- var card = new Card
- {
- Id = "test-id",
- OracleId = "oracle-1",
- SetId = "set-1",
- SetCode = "TST",
- Name = "Test Card",
- CollectorNumber = "1",
- Hash = new byte[] { 0x01, 0x02, 0x03 },
- ImageUri = "https://example.com/image.jpg"
- };
-
- await _database.InsertCardAsync(card);
- var retrieved = await _database.GetCardByIdAsync("test-id");
-
- Assert.NotNull(retrieved);
- Assert.Equal("Test Card", retrieved.Name);
- Assert.Equal("TST", retrieved.SetCode);
- Assert.Equal(card.Hash, retrieved.Hash);
- }
-
- [Fact]
- public async Task InsertCardBatch_InsertsAllCards()
- {
- // Insert oracle first
- var oracle = new Oracle { Id = "oracle-batch", Name = "Batch Card" };
- await _database.InsertOracleAsync(oracle);
-
- var set = new Set { Id = "set-batch", Code = "TST", Name = "Test Set" };
- await _database.InsertSetAsync(set);
-
- var cards = Enumerable.Range(0, 100).Select(i => new Card
- {
- Id = $"card-{i}",
- OracleId = "oracle-batch",
- SetId = "set-batch",
- SetCode = "TST",
- Name = $"Card {i}",
- Hash = new byte[] { (byte)i }
- }).ToList();
-
- await _database.InsertCardBatchAsync(cards);
- var count = await _database.GetCardCountAsync();
-
- Assert.Equal(100, count);
- }
-
- [Fact]
- public async Task GetAllCards_ReturnsAllCards()
- {
- var oracle = new Oracle { Id = "oracle-all", Name = "All Card" };
- await _database.InsertOracleAsync(oracle);
-
- var set = new Set { Id = "set-all", Code = "TST", Name = "Test Set" };
- await _database.InsertSetAsync(set);
-
- var cards = Enumerable.Range(0, 10).Select(i => new Card
- {
- Id = $"card-{i}",
- OracleId = "oracle-all",
- SetId = "set-all",
- SetCode = "TST",
- Name = $"Card {i}",
- Hash = new byte[] { (byte)i }
- }).ToList();
-
- await _database.InsertCardBatchAsync(cards);
- var all = await _database.GetAllCardsAsync();
-
- Assert.Equal(10, all.Count);
- }
-
- [Fact]
- public async Task GetCardsByOracleId_ReturnsAllPrintings()
- {
- var oracle = new Oracle { Id = "oracle-multi", Name = "Multi Print Card" };
- await _database.InsertOracleAsync(oracle);
-
- var set1 = new Set { Id = "set-1", Code = "S1", Name = "Set 1" };
- var set2 = new Set { Id = "set-2", Code = "S2", Name = "Set 2" };
- await _database.InsertSetAsync(set1);
- await _database.InsertSetAsync(set2);
-
- var cards = new[]
- {
- new Card { Id = "print-1", OracleId = "oracle-multi", SetId = "set-1", SetCode = "S1", Name = "Multi Print Card", Hash = new byte[] { 0x01 } },
- new Card { Id = "print-2", OracleId = "oracle-multi", SetId = "set-2", SetCode = "S2", Name = "Multi Print Card", Hash = new byte[] { 0x02 } },
- };
-
- await _database.InsertCardBatchAsync(cards);
- var printings = await _database.GetCardsByOracleIdAsync("oracle-multi");
-
- Assert.Equal(2, printings.Count);
- }
-
- [Fact]
- public async Task Metadata_SetAndGet()
- {
- await _database.SetMetadataAsync("test_key", "test_value");
- var value = await _database.GetMetadataAsync("test_key");
-
- Assert.Equal("test_value", value);
- }
-
- [Fact]
- public async Task ClearCards_RemovesAllCards()
- {
- var oracle = new Oracle { Id = "oracle-clear", Name = "Clear Card" };
- await _database.InsertOracleAsync(oracle);
-
- var set = new Set { Id = "set-clear", Code = "TST", Name = "Test Set" };
- await _database.InsertSetAsync(set);
-
- var cards = Enumerable.Range(0, 10).Select(i => new Card
- {
- Id = $"card-{i}",
- OracleId = "oracle-clear",
- SetId = "set-clear",
- SetCode = "TST",
- Name = $"Card {i}",
- Hash = new byte[] { (byte)i }
- }).ToList();
-
- await _database.InsertCardBatchAsync(cards);
- await _database.ClearCardsAsync();
- var count = await _database.GetCardCountAsync();
-
- Assert.Equal(0, count);
- }
-
- [Fact]
- public async Task InsertCard_DuplicateId_Updates()
- {
- var oracle = new Oracle { Id = "oracle-dup", Name = "Dup Card" };
- await _database.InsertOracleAsync(oracle);
-
- var set = new Set { Id = "set-dup", Code = "TST", Name = "Test Set" };
- await _database.InsertSetAsync(set);
-
- var card1 = new Card
- {
- Id = "duplicate-id",
- OracleId = "oracle-dup",
- SetId = "set-dup",
- SetCode = "TST",
- Name = "Original Name",
- Hash = new byte[] { 0x01 }
- };
-
- var card2 = new Card
- {
- Id = "duplicate-id",
- OracleId = "oracle-dup",
- SetId = "set-dup",
- SetCode = "TST",
- Name = "Updated Name",
- Hash = new byte[] { 0x02 }
- };
-
- await _database.InsertCardAsync(card1);
- await _database.InsertCardAsync(card2);
-
- var retrieved = await _database.GetCardByIdAsync("duplicate-id");
-
- Assert.NotNull(retrieved);
- Assert.Equal("Updated Name", retrieved.Name);
- Assert.Equal(new byte[] { 0x02 }, retrieved.Hash);
- }
-
- [Fact]
- public async Task InsertOracle_ThenRetrieveByName()
- {
- var oracle = new Oracle
- {
- Id = "oracle-name",
- Name = "Lightning Bolt",
- ManaCost = "{R}",
- Cmc = 1,
- TypeLine = "Instant",
- OracleText = "Lightning Bolt deals 3 damage to any target."
- };
-
- await _database.InsertOracleAsync(oracle);
- var retrieved = await _database.GetOracleByNameAsync("Lightning Bolt");
-
- Assert.NotNull(retrieved);
- Assert.Equal("{R}", retrieved.ManaCost);
- Assert.Equal(1, retrieved.Cmc);
- }
-
- [Fact]
- public async Task InsertSet_ThenRetrieveByCode()
- {
- var set = new Set
- {
- Id = "set-lea",
- Code = "lea",
- Name = "Limited Edition Alpha",
- SetType = "expansion",
- ReleasedAt = "1993-08-05",
- CardCount = 295
- };
-
- await _database.InsertSetAsync(set);
- var retrieved = await _database.GetSetByCodeAsync("lea");
-
- Assert.NotNull(retrieved);
- Assert.Equal("Limited Edition Alpha", retrieved.Name);
- Assert.Equal(295, retrieved.CardCount);
- }
-
- [Fact]
- public async Task GetCardsWithHash_OnlyReturnsCardsWithHash()
- {
- var oracle = new Oracle { Id = "oracle-hash", Name = "Hash Card" };
- await _database.InsertOracleAsync(oracle);
-
- var set = new Set { Id = "set-hash", Code = "TST", Name = "Test Set" };
- await _database.InsertSetAsync(set);
-
- var cardWithHash = new Card
- {
- Id = "card-with-hash",
- OracleId = "oracle-hash",
- SetId = "set-hash",
- SetCode = "TST",
- Name = "Has Hash",
- Hash = new byte[] { 0x01 }
- };
-
- var cardWithoutHash = new Card
- {
- Id = "card-no-hash",
- OracleId = "oracle-hash",
- SetId = "set-hash",
- SetCode = "TST",
- Name = "No Hash",
- Hash = null
- };
-
- await _database.InsertCardAsync(cardWithHash);
- await _database.InsertCardAsync(cardWithoutHash);
-
- var cardsWithHash = await _database.GetCardsWithHashAsync();
-
- Assert.Single(cardsWithHash);
- Assert.Equal("card-with-hash", cardsWithHash[0].Id);
- }
-
- public void Dispose()
- {
- _database.Dispose();
- SqliteConnection.ClearAllPools();
- try
- {
- if (File.Exists(_dbPath))
- {
- File.Delete(_dbPath);
- }
- }
- catch (IOException)
- {
- }
- }
-}
diff --git a/test/Scry.Tests/CardRecognitionTests.cs b/test/Scry.Tests/CardRecognitionTests.cs
deleted file mode 100644
index 005abbe..0000000
--- a/test/Scry.Tests/CardRecognitionTests.cs
+++ /dev/null
@@ -1,250 +0,0 @@
-using Microsoft.Data.Sqlite;
-using Scry.Core.Data;
-using Scry.Core.Imaging;
-using Scry.Core.Models;
-using Scry.Core.Recognition;
-using SkiaSharp;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Scry.Tests;
-
-public class CardRecognitionTests : IDisposable
-{
- private readonly ITestOutputHelper _output;
- private readonly string _dbPath;
- private readonly CardDatabase _database;
- private readonly CardRecognitionService _recognitionService;
-
- public CardRecognitionTests(ITestOutputHelper output)
- {
- _output = output;
- _dbPath = Path.Combine(Path.GetTempPath(), $"scry_recognition_test_{Guid.NewGuid()}.db");
- _database = new CardDatabase(_dbPath);
- _recognitionService = new CardRecognitionService(_database);
- }
-
- [Fact]
- public async Task RecognizeAsync_EmptyDatabase_ReturnsFailed()
- {
- using var bitmap = CreateTestBitmap(100, 100);
-
- var result = await _recognitionService.RecognizeAsync(bitmap);
-
- Assert.False(result.Success);
- Assert.Contains("No cards", result.ErrorMessage);
- }
-
- [Fact]
- public async Task RecognizeAsync_ExactMatch_ReturnsSuccess()
- {
- using var bitmap = CreateTestBitmap(100, 100);
- var hash = _recognitionService.ComputeHash(bitmap);
-
- // Insert oracle and set first
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-test", Name = "Test Card" });
- await _database.InsertSetAsync(new Set { Id = "set-test", Code = "TST", Name = "Test Set" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "test-card",
- OracleId = "oracle-test",
- SetId = "set-test",
- SetCode = "TST",
- Name = "Test Card",
- Hash = hash,
- ImageUri = "https://example.com/test.jpg"
- });
- await _recognitionService.InvalidateCacheAsync();
-
- var result = await _recognitionService.RecognizeAsync(bitmap);
-
- Assert.True(result.Success);
- Assert.Equal("Test Card", result.Card?.Name);
- Assert.Equal(1.0f, result.Confidence);
- Assert.Equal(0, result.HammingDistance);
- }
-
- [Theory]
- [InlineData("reference/brainstorm.png")]
- [InlineData("reference/force_of_will.png")]
- [InlineData("single_cards/llanowar_elves.jpg")]
- public async Task RecognizeAsync_ReferenceImage_SelfMatch(string imagePath)
- {
- var fullPath = Path.Combine("TestImages", imagePath);
- if (!File.Exists(fullPath))
- {
- _output.WriteLine($"Skipping test - file not found: {fullPath}");
- return;
- }
-
- using var bitmap = SKBitmap.Decode(fullPath);
- Assert.NotNull(bitmap);
-
- var hash = _recognitionService.ComputeHash(bitmap);
- var cardName = Path.GetFileNameWithoutExtension(imagePath);
-
- await _database.InsertOracleAsync(new Oracle { Id = $"oracle-{cardName}", Name = cardName });
- await _database.InsertSetAsync(new Set { Id = "set-ref", Code = "REF", Name = "Reference Set" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = cardName,
- OracleId = $"oracle-{cardName}",
- SetId = "set-ref",
- SetCode = "REF",
- Name = cardName,
- Hash = hash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- var result = await _recognitionService.RecognizeAsync(bitmap);
-
- Assert.True(result.Success, $"Recognition failed: {result.ErrorMessage}");
- Assert.Equal(cardName, result.Card?.Name);
- Assert.True(result.Confidence >= 0.85f);
-
- _output.WriteLine($"Matched: {cardName}, Confidence: {result.Confidence:P0}, Distance: {result.HammingDistance}");
- }
-
- [Fact]
- public async Task RecognizeAsync_SerraAngelFromDatabase_Matches()
- {
- // Find the solution root (where .git is)
- var currentDir = Directory.GetCurrentDirectory();
- var rootDir = currentDir;
- while (!Directory.Exists(Path.Combine(rootDir, ".git")) && Directory.GetParent(rootDir) != null)
- {
- rootDir = Directory.GetParent(rootDir)!.FullName;
- }
-
- var dbPath = Path.Combine(rootDir, "src", "Scry.App", "Resources", "Raw", "card_hashes.db");
- if (!File.Exists(dbPath))
- {
- _output.WriteLine($"Skipping - database not found at {dbPath}");
- return;
- }
-
- var imagePath = Path.Combine(rootDir, "TestImages", "reference_alpha", "serra_angel.jpg");
- if (!File.Exists(imagePath))
- {
- _output.WriteLine($"Skipping - image not found at {imagePath}");
- return;
- }
-
- using var testDb = new CardDatabase(dbPath);
- using var testRecognition = new CardRecognitionService(testDb);
-
- using var bitmap = SKBitmap.Decode(imagePath);
- Assert.NotNull(bitmap);
-
- // First, just compute hash and check distance manually
- var queryHash = testRecognition.ComputeHash(bitmap);
- var allCards = await testDb.GetCardsWithHashAsync();
-
- _output.WriteLine($"Query hash length: {queryHash.Length} bytes");
- _output.WriteLine($"Database has {allCards.Count} cards with hashes");
-
- // Find Serra Angel and compute distance
- var serraCard = allCards.FirstOrDefault(c => c.Name == "Serra Angel");
- if (serraCard?.Hash != null)
- {
- var distance = PerceptualHash.HammingDistance(queryHash, serraCard.Hash);
- _output.WriteLine($"Serra Angel hash length: {serraCard.Hash.Length} bytes");
- _output.WriteLine($"Distance to Serra Angel: {distance}");
- }
-
- // Find the actual best match
- int bestDistance = int.MaxValue;
- string? bestName = null;
- foreach (var card in allCards)
- {
- if (card.Hash == null || card.Hash.Length != queryHash.Length) continue;
- var dist = PerceptualHash.HammingDistance(queryHash, card.Hash);
- if (dist < bestDistance)
- {
- bestDistance = dist;
- bestName = card.Name;
- }
- }
- _output.WriteLine($"Best match: {bestName}, distance: {bestDistance}");
-
- // Now try actual recognition
- var result = await testRecognition.RecognizeAsync(bitmap);
-
- if (result.Success)
- {
- _output.WriteLine($"Recognition succeeded: {result.Card?.Name}, confidence: {result.Confidence:P0}");
- Assert.Equal("Serra Angel", result.Card?.Name);
- }
- else
- {
- _output.WriteLine($"Recognition failed: {result.ErrorMessage}");
- // For debugging - this should be 0 since we're using the exact same image
- Assert.Fail($"Expected to match Serra Angel, but got: {result.ErrorMessage}");
- }
- }
-
- [Fact]
- public async Task RecognizeAsync_MeasuresProcessingTime()
- {
- using var bitmap = CreateTestBitmap(200, 300);
- var hash = _recognitionService.ComputeHash(bitmap);
-
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-timing", Name = "Timing Test Card" });
- await _database.InsertSetAsync(new Set { Id = "set-timing", Code = "TST", Name = "Test Set" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "timing-test",
- OracleId = "oracle-timing",
- SetId = "set-timing",
- SetCode = "TST",
- Name = "Timing Test Card",
- Hash = hash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- var result = await _recognitionService.RecognizeAsync(bitmap);
-
- Assert.True(result.Success);
- Assert.True(result.ProcessingTime.TotalMilliseconds > 0);
- _output.WriteLine($"Processing time: {result.ProcessingTime.TotalMilliseconds:F2}ms");
- }
-
- private static SKBitmap CreateTestBitmap(int width, int height)
- {
- var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- var random = new Random(42);
-
- for (var y = 0; y < height; y++)
- {
- for (var x = 0; x < width; x++)
- {
- var r = (byte)random.Next(256);
- var g = (byte)random.Next(256);
- var b = (byte)random.Next(256);
- bitmap.SetPixel(x, y, new SKColor(r, g, b));
- }
- }
-
- return bitmap;
- }
-
- public void Dispose()
- {
- _recognitionService.Dispose();
- _database.Dispose();
- SqliteConnection.ClearAllPools();
- try
- {
- if (File.Exists(_dbPath))
- {
- File.Delete(_dbPath);
- }
- }
- catch (IOException)
- {
- }
- }
-}
diff --git a/test/Scry.Tests/ImagePreprocessorTests.cs b/test/Scry.Tests/ImagePreprocessorTests.cs
deleted file mode 100644
index 8498443..0000000
--- a/test/Scry.Tests/ImagePreprocessorTests.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using Scry.Core.Imaging;
-using SkiaSharp;
-using Xunit;
-
-namespace Scry.Tests;
-
-public class ImagePreprocessorTests
-{
- [Fact]
- public void Resize_ProducesCorrectDimensions()
- {
- using var bitmap = CreateTestBitmap(100, 100);
-
- using var resized = ImagePreprocessor.Resize(bitmap, 32, 32);
-
- Assert.Equal(32, resized.Width);
- Assert.Equal(32, resized.Height);
- }
-
- [Fact]
- public void ToGrayscale_ProducesGrayscaleImage()
- {
- using var bitmap = CreateColorBitmap(10, 10);
-
- using var grayscale = ImagePreprocessor.ToGrayscale(bitmap);
-
- Assert.Equal(10, grayscale.Width);
- Assert.Equal(10, grayscale.Height);
- }
-
- [Fact]
- public void ApplyClahe_PreservesDimensions()
- {
- using var bitmap = CreateTestBitmap(64, 64);
-
- using var result = ImagePreprocessor.ApplyClahe(bitmap);
-
- Assert.Equal(64, result.Width);
- Assert.Equal(64, result.Height);
- }
-
- [Fact]
- public void ApplyClahe_EnhancesContrast()
- {
- using var bitmap = CreateLowContrastBitmap(64, 64);
-
- using var result = ImagePreprocessor.ApplyClahe(bitmap);
-
- Assert.NotNull(result);
- Assert.Equal(bitmap.Width, result.Width);
- Assert.Equal(bitmap.Height, result.Height);
- }
-
- [Theory]
- [InlineData("varying_quality/test1.jpg")]
- [InlineData("low_light/glare_toploader.png")]
- [InlineData("foil/rainbow_foil_secret_lair.jpg")]
- public void ApplyClahe_RealImages_DoesNotCrash(string imagePath)
- {
- var fullPath = Path.Combine("TestImages", imagePath);
- if (!File.Exists(fullPath))
- {
- return;
- }
-
- using var bitmap = SKBitmap.Decode(fullPath);
- Assert.NotNull(bitmap);
-
- using var result = ImagePreprocessor.ApplyClahe(bitmap);
-
- Assert.Equal(bitmap.Width, result.Width);
- Assert.Equal(bitmap.Height, result.Height);
- }
-
- private static SKBitmap CreateTestBitmap(int width, int height)
- {
- var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- using var canvas = new SKCanvas(bitmap);
- canvas.Clear(SKColors.Gray);
- return bitmap;
- }
-
- private static SKBitmap CreateColorBitmap(int width, int height)
- {
- var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- for (var y = 0; y < height; y++)
- {
- for (var x = 0; x < width; x++)
- {
- var r = (byte)(x * 255 / width);
- var g = (byte)(y * 255 / height);
- var b = (byte)((x + y) * 127 / (width + height));
- bitmap.SetPixel(x, y, new SKColor(r, g, b));
- }
- }
- return bitmap;
- }
-
- private static SKBitmap CreateLowContrastBitmap(int width, int height)
- {
- var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- for (var y = 0; y < height; y++)
- {
- for (var x = 0; x < width; x++)
- {
- var gray = (byte)(120 + (x + y) % 20);
- bitmap.SetPixel(x, y, new SKColor(gray, gray, gray));
- }
- }
- return bitmap;
- }
-
- private static (byte Min, byte Max) GetLuminanceRange(SKBitmap bitmap)
- {
- byte min = 255;
- byte max = 0;
-
- for (var y = 0; y < bitmap.Height; y++)
- {
- for (var x = 0; x < bitmap.Width; x++)
- {
- var pixel = bitmap.GetPixel(x, y);
- var luminance = (byte)(0.299 * pixel.Red + 0.587 * pixel.Green + 0.114 * pixel.Blue);
- min = Math.Min(min, luminance);
- max = Math.Max(max, luminance);
- }
- }
-
- return (min, max);
- }
-}
diff --git a/test/Scry.Tests/PerceptualHashTests.cs b/test/Scry.Tests/PerceptualHashTests.cs
deleted file mode 100644
index 6553bf5..0000000
--- a/test/Scry.Tests/PerceptualHashTests.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using Scry.Core.Imaging;
-using SkiaSharp;
-using Xunit;
-
-namespace Scry.Tests;
-
-public class PerceptualHashTests
-{
- [Fact]
- public void ComputeHash_ReturnsConsistentHash()
- {
- using var bitmap = CreateTestBitmap(32, 32, SKColors.Red);
-
- var hash1 = PerceptualHash.ComputeHash(bitmap);
- var hash2 = PerceptualHash.ComputeHash(bitmap);
-
- Assert.Equal(hash1, hash2);
- }
-
- [Fact]
- public void ComputeColorHash_Returns24Bytes()
- {
- using var bitmap = CreateTestBitmap(32, 32, SKColors.Blue);
-
- var hash = PerceptualHash.ComputeColorHash(bitmap);
-
- Assert.Equal(24, hash.Length);
- }
-
- [Fact]
- public void HammingDistance_IdenticalHashes_ReturnsZero()
- {
- var hash = new byte[] { 0xFF, 0x00, 0xAB, 0xCD };
-
- var distance = PerceptualHash.HammingDistance(hash, hash);
-
- Assert.Equal(0, distance);
- }
-
- [Fact]
- public void HammingDistance_OppositeHashes_ReturnsMaxBits()
- {
- var hash1 = new byte[] { 0x00, 0x00 };
- var hash2 = new byte[] { 0xFF, 0xFF };
-
- var distance = PerceptualHash.HammingDistance(hash1, hash2);
-
- Assert.Equal(16, distance);
- }
-
- [Fact]
- public void HammingDistance_SingleBitDifference()
- {
- var hash1 = new byte[] { 0b00000000 };
- var hash2 = new byte[] { 0b00000001 };
-
- var distance = PerceptualHash.HammingDistance(hash1, hash2);
-
- Assert.Equal(1, distance);
- }
-
- [Fact]
- public void CalculateConfidence_ZeroDistance_ReturnsOne()
- {
- var confidence = PerceptualHash.CalculateConfidence(0, 192);
-
- Assert.Equal(1.0f, confidence);
- }
-
- [Fact]
- public void CalculateConfidence_HalfDistance_ReturnsHalf()
- {
- var confidence = PerceptualHash.CalculateConfidence(96, 192);
-
- Assert.Equal(0.5f, confidence);
- }
-
- [Theory]
- [InlineData("reference/brainstorm.png")]
- [InlineData("reference/force_of_will.png")]
- [InlineData("single_cards/llanowar_elves.jpg")]
- public void ComputeColorHash_RealImages_ProducesValidHash(string imagePath)
- {
- var fullPath = Path.Combine("TestImages", imagePath);
- if (!File.Exists(fullPath))
- {
- return;
- }
-
- using var bitmap = SKBitmap.Decode(fullPath);
- Assert.NotNull(bitmap);
-
- var hash = PerceptualHash.ComputeColorHash(bitmap);
-
- Assert.Equal(24, hash.Length);
- Assert.True(hash.Any(b => b != 0), "Hash should not be all zeros");
- }
-
- [Fact]
- public void SimilarImages_HaveLowHammingDistance()
- {
- using var bitmap1 = CreateGradientBitmap(32, 32, SKColors.Red, SKColors.Blue);
- using var bitmap2 = CreateGradientBitmap(32, 32, SKColors.Red, SKColors.Blue);
-
- var hash1 = PerceptualHash.ComputeColorHash(bitmap1);
- var hash2 = PerceptualHash.ComputeColorHash(bitmap2);
-
- var distance = PerceptualHash.HammingDistance(hash1, hash2);
-
- Assert.Equal(0, distance);
- }
-
- [Fact]
- public void DifferentImages_HaveHighHammingDistance()
- {
- using var bitmap1 = CreateTestBitmap(32, 32, SKColors.Red);
- using var bitmap2 = CreateTestBitmap(32, 32, SKColors.Blue);
-
- var hash1 = PerceptualHash.ComputeColorHash(bitmap1);
- var hash2 = PerceptualHash.ComputeColorHash(bitmap2);
-
- var distance = PerceptualHash.HammingDistance(hash1, hash2);
-
- Assert.True(distance > 10, $"Expected distance > 10, got {distance}");
- }
-
- private static SKBitmap CreateTestBitmap(int width, int height, SKColor color)
- {
- var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- using var canvas = new SKCanvas(bitmap);
- canvas.Clear(color);
- return bitmap;
- }
-
- private static SKBitmap CreateGradientBitmap(int width, int height, SKColor start, SKColor end)
- {
- var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
- using var canvas = new SKCanvas(bitmap);
- using var paint = new SKPaint();
- paint.Shader = SKShader.CreateLinearGradient(
- new SKPoint(0, 0),
- new SKPoint(width, height),
- new[] { start, end },
- SKShaderTileMode.Clamp);
- canvas.DrawRect(0, 0, width, height, paint);
- return bitmap;
- }
-}
diff --git a/test/Scry.Tests/RobustnessAnalysisTests.cs b/test/Scry.Tests/RobustnessAnalysisTests.cs
deleted file mode 100644
index c335347..0000000
--- a/test/Scry.Tests/RobustnessAnalysisTests.cs
+++ /dev/null
@@ -1,505 +0,0 @@
-using Scry.Core.Data;
-using Scry.Core.Imaging;
-using Scry.Core.Models;
-using Scry.Core.Recognition;
-using SkiaSharp;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Scry.Tests;
-
-///
-/// Tests to analyze robustness of perceptual hashing under various camera scanning conditions.
-///
-public class RobustnessAnalysisTests : IDisposable
-{
- private readonly ITestOutputHelper _output;
- private readonly string _dbPath;
- private readonly CardDatabase _database;
- private readonly CardRecognitionService _recognitionService;
-
- public RobustnessAnalysisTests(ITestOutputHelper output)
- {
- _output = output;
- _dbPath = Path.Combine(Path.GetTempPath(), $"scry_robustness_test_{Guid.NewGuid()}.db");
- _database = new CardDatabase(_dbPath);
- _recognitionService = new CardRecognitionService(_database);
- }
-
- ///
- /// Test how rotation affects hash matching.
- /// pHash uses DCT which is NOT rotation invariant - this tests the impact.
- ///
- [Theory]
- [InlineData(5)] // Slight tilt
- [InlineData(15)] // Moderate tilt
- [InlineData(45)] // Significant rotation
- [InlineData(90)] // Portrait vs landscape
- [InlineData(180)] // Upside down
- public async Task Rotation_ImpactOnHashDistance(int rotationDegrees)
- {
- var imagePath = FindTestImage("reference_alpha/serra_angel.jpg");
- if (imagePath == null)
- {
- _output.WriteLine("Test image not found, skipping");
- return;
- }
-
- using var original = SKBitmap.Decode(imagePath);
- Assert.NotNull(original);
-
- // Compute hash of original
- var originalHash = _recognitionService.ComputeHash(original);
-
- // Register original in database
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
- await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "serra-angel",
- OracleId = "oracle-serra",
- SetId = "set-lea",
- SetCode = "LEA",
- Name = "Serra Angel",
- Hash = originalHash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- // Rotate the image
- using var rotated = RotateImage(original, rotationDegrees);
- var rotatedHash = _recognitionService.ComputeHash(rotated);
-
- var distance = PerceptualHash.HammingDistance(originalHash, rotatedHash);
- var confidence = PerceptualHash.CalculateConfidence(distance, 192);
-
- // Try recognition
- var result = await _recognitionService.RecognizeAsync(rotated);
-
- _output.WriteLine($"Rotation: {rotationDegrees}°");
- _output.WriteLine($" Hamming distance: {distance}/192 bits");
- _output.WriteLine($" Confidence: {confidence:P0}");
- _output.WriteLine($" Recognition: {(result.Success ? "SUCCESS" : "FAILED")}");
- _output.WriteLine($" Result: {result.Card?.Name ?? result.ErrorMessage}");
-
- // Document expected behavior
- if (rotationDegrees <= 5)
- {
- // Small rotations might still work
- _output.WriteLine($" [Small rotation - may still match]");
- }
- else
- {
- // pHash is NOT rotation invariant - this is expected to fail
- _output.WriteLine($" [pHash is NOT rotation invariant - failure expected]");
- }
- }
-
- ///
- /// Test how scaling/distance affects hash matching.
- ///
- [Theory]
- [InlineData(0.25f)] // Very small in frame (far away)
- [InlineData(0.50f)] // Half size
- [InlineData(0.75f)] // 3/4 size
- [InlineData(1.25f)] // Slightly larger
- [InlineData(2.0f)] // Double size (close up)
- public async Task Scale_ImpactOnHashDistance(float scaleFactor)
- {
- var imagePath = FindTestImage("reference_alpha/serra_angel.jpg");
- if (imagePath == null)
- {
- _output.WriteLine("Test image not found, skipping");
- return;
- }
-
- using var original = SKBitmap.Decode(imagePath);
- Assert.NotNull(original);
-
- var originalHash = _recognitionService.ComputeHash(original);
-
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
- await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "serra-angel",
- OracleId = "oracle-serra",
- SetId = "set-lea",
- SetCode = "LEA",
- Name = "Serra Angel",
- Hash = originalHash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- // Scale the image
- var newWidth = (int)(original.Width * scaleFactor);
- var newHeight = (int)(original.Height * scaleFactor);
- using var scaled = ImagePreprocessor.Resize(original, newWidth, newHeight);
- var scaledHash = _recognitionService.ComputeHash(scaled);
-
- var distance = PerceptualHash.HammingDistance(originalHash, scaledHash);
- var confidence = PerceptualHash.CalculateConfidence(distance, 192);
-
- var result = await _recognitionService.RecognizeAsync(scaled);
-
- _output.WriteLine($"Scale: {scaleFactor:P0}");
- _output.WriteLine($" Original: {original.Width}x{original.Height}");
- _output.WriteLine($" Scaled: {newWidth}x{newHeight}");
- _output.WriteLine($" Hamming distance: {distance}/192 bits");
- _output.WriteLine($" Confidence: {confidence:P0}");
- _output.WriteLine($" Recognition: {(result.Success ? "SUCCESS" : "FAILED")}");
-
- // pHash should be relatively scale-invariant since it resizes to 32x32 internally
- _output.WriteLine($" [pHash resizes internally - should be scale invariant]");
- }
-
- ///
- /// Test impact of card being placed on different backgrounds.
- ///
- [Theory]
- [InlineData(0, 0, 0)] // Black background
- [InlineData(255, 255, 255)] // White background
- [InlineData(128, 128, 128)] // Gray background
- [InlineData(139, 69, 19)] // Brown (wood table)
- [InlineData(0, 128, 0)] // Green (playmat)
- public async Task Background_ImpactOnHashDistance(byte r, byte g, byte b)
- {
- var imagePath = FindTestImage("reference_alpha/serra_angel.jpg");
- if (imagePath == null)
- {
- _output.WriteLine("Test image not found, skipping");
- return;
- }
-
- using var original = SKBitmap.Decode(imagePath);
- Assert.NotNull(original);
-
- var originalHash = _recognitionService.ComputeHash(original);
-
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
- await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "serra-angel",
- OracleId = "oracle-serra",
- SetId = "set-lea",
- SetCode = "LEA",
- Name = "Serra Angel",
- Hash = originalHash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- // Create image with card on colored background (with padding)
- using var withBackground = PlaceOnBackground(original, new SKColor(r, g, b), 100);
- var bgHash = _recognitionService.ComputeHash(withBackground);
-
- var distance = PerceptualHash.HammingDistance(originalHash, bgHash);
- var confidence = PerceptualHash.CalculateConfidence(distance, 192);
-
- var result = await _recognitionService.RecognizeAsync(withBackground);
-
- _output.WriteLine($"Background: RGB({r},{g},{b})");
- _output.WriteLine($" Image size: {withBackground.Width}x{withBackground.Height}");
- _output.WriteLine($" Hamming distance: {distance}/192 bits");
- _output.WriteLine($" Confidence: {confidence:P0}");
- _output.WriteLine($" Recognition: {(result.Success ? "SUCCESS" : "FAILED")}");
-
- // Background WILL affect hash significantly since no card detection/cropping is done
- _output.WriteLine($" [No card detection - background included in hash - CRITICAL ISSUE]");
- }
-
- ///
- /// Test brightness variations (simulating different lighting).
- ///
- [Theory]
- [InlineData(-50)] // Darker
- [InlineData(-25)] // Slightly darker
- [InlineData(25)] // Slightly brighter
- [InlineData(50)] // Brighter
- [InlineData(100)] // Very bright (overexposed)
- public async Task Brightness_ImpactOnHashDistance(int adjustment)
- {
- var imagePath = FindTestImage("reference_alpha/serra_angel.jpg");
- if (imagePath == null)
- {
- _output.WriteLine("Test image not found, skipping");
- return;
- }
-
- using var original = SKBitmap.Decode(imagePath);
- Assert.NotNull(original);
-
- var originalHash = _recognitionService.ComputeHash(original);
-
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
- await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "serra-angel",
- OracleId = "oracle-serra",
- SetId = "set-lea",
- SetCode = "LEA",
- Name = "Serra Angel",
- Hash = originalHash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- using var adjusted = AdjustBrightness(original, adjustment);
- var adjustedHash = _recognitionService.ComputeHash(adjusted);
-
- var distance = PerceptualHash.HammingDistance(originalHash, adjustedHash);
- var confidence = PerceptualHash.CalculateConfidence(distance, 192);
-
- var result = await _recognitionService.RecognizeAsync(adjusted);
-
- _output.WriteLine($"Brightness adjustment: {adjustment:+#;-#;0}");
- _output.WriteLine($" Hamming distance: {distance}/192 bits");
- _output.WriteLine($" Confidence: {confidence:P0}");
- _output.WriteLine($" Recognition: {(result.Success ? "SUCCESS" : "FAILED")}");
-
- // CLAHE should help normalize lighting
- _output.WriteLine($" [CLAHE preprocessing should help normalize lighting]");
- }
-
- ///
- /// Test how perspective distortion affects matching.
- ///
- [Theory]
- [InlineData(5)] // Slight perspective
- [InlineData(15)] // Moderate perspective
- [InlineData(30)] // Significant perspective
- public async Task Perspective_ImpactOnHashDistance(int perspectiveDegrees)
- {
- var imagePath = FindTestImage("reference_alpha/serra_angel.jpg");
- if (imagePath == null)
- {
- _output.WriteLine("Test image not found, skipping");
- return;
- }
-
- using var original = SKBitmap.Decode(imagePath);
- Assert.NotNull(original);
-
- var originalHash = _recognitionService.ComputeHash(original);
-
- await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
- await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
-
- await _database.InsertCardAsync(new Card
- {
- Id = "serra-angel",
- OracleId = "oracle-serra",
- SetId = "set-lea",
- SetCode = "LEA",
- Name = "Serra Angel",
- Hash = originalHash
- });
- await _recognitionService.InvalidateCacheAsync();
-
- // Apply perspective transform (shear as approximation)
- using var perspective = ApplyPerspective(original, perspectiveDegrees);
- var perspectiveHash = _recognitionService.ComputeHash(perspective);
-
- var distance = PerceptualHash.HammingDistance(originalHash, perspectiveHash);
- var confidence = PerceptualHash.CalculateConfidence(distance, 192);
-
- var result = await _recognitionService.RecognizeAsync(perspective);
-
- _output.WriteLine($"Perspective: {perspectiveDegrees}° tilt");
- _output.WriteLine($" Hamming distance: {distance}/192 bits");
- _output.WriteLine($" Confidence: {confidence:P0}");
- _output.WriteLine($" Recognition: {(result.Success ? "SUCCESS" : "FAILED")}");
-
- _output.WriteLine($" [No perspective correction - distortion affects hash]");
- }
-
- ///
- /// Test real-world photos vs reference images.
- ///
- [Fact]
- public async Task RealPhotos_VsReferenceImages()
- {
- // Find the production database
- var currentDir = Directory.GetCurrentDirectory();
- var rootDir = currentDir;
- while (!Directory.Exists(Path.Combine(rootDir, ".git")) && Directory.GetParent(rootDir) != null)
- {
- rootDir = Directory.GetParent(rootDir)!.FullName;
- }
-
- var dbPath = Path.Combine(rootDir, "src", "Scry.App", "Resources", "Raw", "card_hashes.db");
- if (!File.Exists(dbPath))
- {
- _output.WriteLine($"Database not found at {dbPath}");
- return;
- }
-
- using var prodDb = new CardDatabase(dbPath);
- using var prodRecognition = new CardRecognitionService(prodDb);
-
- var testImagesDir = Path.Combine(rootDir, "TestImages");
- var categoriesToTest = new[] { "real_photos", "varying_quality", "angled", "low_light" };
-
- _output.WriteLine("=== Real-World Recognition Test ===");
- _output.WriteLine($"Database cards: {(await prodDb.GetCardsWithHashAsync()).Count}");
- _output.WriteLine("");
-
- foreach (var category in categoriesToTest)
- {
- var categoryPath = Path.Combine(testImagesDir, category);
- if (!Directory.Exists(categoryPath))
- continue;
-
- _output.WriteLine($"--- {category} ---");
-
- var imageFiles = Directory.GetFiles(categoryPath)
- .Where(f => new[] { ".jpg", ".jpeg", ".png", ".webp" }
- .Contains(Path.GetExtension(f).ToLowerInvariant()))
- .Take(5)
- .ToList();
-
- var successes = 0;
- var failures = 0;
-
- foreach (var file in imageFiles)
- {
- try
- {
- using var bitmap = SKBitmap.Decode(file);
- if (bitmap == null) continue;
-
- var result = await prodRecognition.RecognizeAsync(bitmap);
- var fileName = Path.GetFileName(file);
-
- if (result.Success)
- {
- successes++;
- _output.WriteLine($" [OK] {fileName} -> {result.Card?.Name} ({result.Confidence:P0})");
- }
- else
- {
- failures++;
- _output.WriteLine($" [FAIL] {fileName} -> {result.ErrorMessage}");
- }
- }
- catch (Exception ex)
- {
- _output.WriteLine($" [ERROR] {Path.GetFileName(file)}: {ex.Message}");
- }
- }
-
- _output.WriteLine($" Results: {successes} OK, {failures} failed");
- _output.WriteLine("");
- }
- }
-
- #region Helper Methods
-
- private static string? FindTestImage(string relativePath)
- {
- var currentDir = Directory.GetCurrentDirectory();
- var rootDir = currentDir;
- while (!Directory.Exists(Path.Combine(rootDir, ".git")) && Directory.GetParent(rootDir) != null)
- {
- rootDir = Directory.GetParent(rootDir)!.FullName;
- }
-
- var fullPath = Path.Combine(rootDir, "TestImages", relativePath);
- return File.Exists(fullPath) ? fullPath : null;
- }
-
- private static SKBitmap RotateImage(SKBitmap original, int degrees)
- {
- var radians = degrees * Math.PI / 180;
- var cos = Math.Abs(Math.Cos(radians));
- var sin = Math.Abs(Math.Sin(radians));
-
- var newWidth = (int)(original.Width * cos + original.Height * sin);
- var newHeight = (int)(original.Width * sin + original.Height * cos);
-
- var rotated = new SKBitmap(newWidth, newHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
-
- using var canvas = new SKCanvas(rotated);
- canvas.Clear(SKColors.White);
- canvas.Translate(newWidth / 2f, newHeight / 2f);
- canvas.RotateDegrees(degrees);
- canvas.Translate(-original.Width / 2f, -original.Height / 2f);
- canvas.DrawBitmap(original, 0, 0);
-
- return rotated;
- }
-
- private static SKBitmap PlaceOnBackground(SKBitmap card, SKColor bgColor, int padding)
- {
- var newWidth = card.Width + padding * 2;
- var newHeight = card.Height + padding * 2;
-
- var result = new SKBitmap(newWidth, newHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
-
- using var canvas = new SKCanvas(result);
- canvas.Clear(bgColor);
- canvas.DrawBitmap(card, padding, padding);
-
- return result;
- }
-
- private static SKBitmap AdjustBrightness(SKBitmap original, int adjustment)
- {
- var result = new SKBitmap(original.Width, original.Height, SKColorType.Rgba8888, SKAlphaType.Premul);
-
- for (var y = 0; y < original.Height; y++)
- {
- for (var x = 0; x < original.Width; x++)
- {
- var pixel = original.GetPixel(x, y);
- var r = (byte)Math.Clamp(pixel.Red + adjustment, 0, 255);
- var g = (byte)Math.Clamp(pixel.Green + adjustment, 0, 255);
- var b = (byte)Math.Clamp(pixel.Blue + adjustment, 0, 255);
- result.SetPixel(x, y, new SKColor(r, g, b, pixel.Alpha));
- }
- }
-
- return result;
- }
-
- private static SKBitmap ApplyPerspective(SKBitmap original, int degrees)
- {
- // Approximate perspective with horizontal shear
- var shearFactor = (float)Math.Tan(degrees * Math.PI / 180);
- var extraWidth = (int)(original.Height * Math.Abs(shearFactor));
-
- var result = new SKBitmap(original.Width + extraWidth, original.Height, SKColorType.Rgba8888, SKAlphaType.Premul);
-
- using var canvas = new SKCanvas(result);
- canvas.Clear(SKColors.White);
-
- var matrix = SKMatrix.CreateSkew(shearFactor, 0);
- if (shearFactor > 0)
- {
- matrix = SKMatrix.Concat(SKMatrix.CreateTranslation(extraWidth, 0), matrix);
- }
-
- canvas.SetMatrix(matrix);
- canvas.DrawBitmap(original, 0, 0);
-
- return result;
- }
-
- #endregion
-
- public void Dispose()
- {
- _recognitionService.Dispose();
- _database.Dispose();
- Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
- try
- {
- if (File.Exists(_dbPath))
- File.Delete(_dbPath);
- }
- catch { }
- }
-}
diff --git a/test/Scry.Tests/Scry.Tests.csproj b/test/Scry.Tests/Scry.Tests.csproj
deleted file mode 100644
index f53b84d..0000000
--- a/test/Scry.Tests/Scry.Tests.csproj
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- net10.0
- enable
- enable
- false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
diff --git a/test/Scry.Tests/TestImageBenchmarks.cs b/test/Scry.Tests/TestImageBenchmarks.cs
deleted file mode 100644
index a164e40..0000000
--- a/test/Scry.Tests/TestImageBenchmarks.cs
+++ /dev/null
@@ -1,232 +0,0 @@
-using Scry.Core.Imaging;
-using SkiaSharp;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Scry.Tests;
-
-public class TestImageBenchmarks
-{
- private readonly ITestOutputHelper _output;
- private static readonly string TestImagesDir = "TestImages";
-
- public TestImageBenchmarks(ITestOutputHelper output)
- {
- _output = output;
- }
-
- [Fact]
- public void ProcessAllTestImages_ComputeHashes()
- {
- if (!Directory.Exists(TestImagesDir))
- {
- _output.WriteLine("TestImages directory not found, skipping benchmark");
- return;
- }
-
- var categories = Directory.GetDirectories(TestImagesDir);
- var results = new List<(string Category, int Count, double AvgTimeMs, int Failures)>();
-
- foreach (var categoryPath in categories)
- {
- var category = Path.GetFileName(categoryPath);
- var imageFiles = GetImageFiles(categoryPath);
-
- if (!imageFiles.Any())
- continue;
-
- var times = new List();
- var failures = 0;
-
- foreach (var file in imageFiles)
- {
- try
- {
- var sw = System.Diagnostics.Stopwatch.StartNew();
-
- using var bitmap = SKBitmap.Decode(file);
- if (bitmap == null)
- {
- failures++;
- continue;
- }
-
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- var hash = PerceptualHash.ComputeColorHash(preprocessed);
-
- sw.Stop();
- times.Add(sw.Elapsed.TotalMilliseconds);
- }
- catch (Exception ex)
- {
- failures++;
- _output.WriteLine($" Failed: {Path.GetFileName(file)} - {ex.Message}");
- }
- }
-
- if (times.Any())
- {
- var avgTime = times.Average();
- results.Add((category, times.Count, avgTime, failures));
- _output.WriteLine($"{category}: {times.Count} images, {avgTime:F1}ms avg, {failures} failures");
- }
- }
-
- _output.WriteLine("");
- _output.WriteLine("=== Summary ===");
- var totalImages = results.Sum(r => r.Count);
- var totalFailures = results.Sum(r => r.Failures);
- var overallAvg = results.SelectMany((r, _) => Enumerable.Repeat(r.AvgTimeMs, r.Count)).Average();
-
- _output.WriteLine($"Total: {totalImages} images processed");
- _output.WriteLine($"Failures: {totalFailures}");
- _output.WriteLine($"Overall avg: {overallAvg:F1}ms per image");
-
- Assert.True(totalImages > 0, "Should process at least some images");
- }
-
- [Theory]
- [InlineData("foil")]
- [InlineData("worn")]
- [InlineData("low_light")]
- [InlineData("foreign")]
- [InlineData("tokens")]
- public void ProcessCategory_AllImagesHash(string category)
- {
- var categoryPath = Path.Combine(TestImagesDir, category);
- if (!Directory.Exists(categoryPath))
- {
- _output.WriteLine($"Category not found: {category}");
- return;
- }
-
- var imageFiles = GetImageFiles(categoryPath);
- _output.WriteLine($"Processing {imageFiles.Count} images in {category}/");
-
- var processed = 0;
- var failed = 0;
-
- foreach (var file in imageFiles)
- {
- var fileName = Path.GetFileName(file);
- try
- {
- using var bitmap = SKBitmap.Decode(file);
- if (bitmap == null)
- {
- _output.WriteLine($" [DECODE FAIL] {fileName}");
- failed++;
- continue;
- }
-
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- var hash = PerceptualHash.ComputeColorHash(preprocessed);
-
- Assert.Equal(24, hash.Length);
- processed++;
- _output.WriteLine($" [OK] {fileName} ({bitmap.Width}x{bitmap.Height})");
- }
- catch (Exception ex)
- {
- _output.WriteLine($" [ERROR] {fileName}: {ex.Message}");
- failed++;
- }
- }
-
- _output.WriteLine($"");
- _output.WriteLine($"Results: {processed} OK, {failed} failed");
-
- Assert.True(processed > 0 || !imageFiles.Any(), $"Should process at least one image in {category}");
- }
-
- [Fact]
- public void HashStability_SameImageProducesSameHash()
- {
- var testFile = Path.Combine(TestImagesDir, "reference", "brainstorm.png");
- if (!File.Exists(testFile))
- {
- testFile = GetImageFiles(TestImagesDir).FirstOrDefault();
- if (testFile == null)
- {
- _output.WriteLine("No test images found");
- return;
- }
- }
-
- using var bitmap = SKBitmap.Decode(testFile);
- Assert.NotNull(bitmap);
-
- var hashes = new List();
- for (var i = 0; i < 5; i++)
- {
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- hashes.Add(PerceptualHash.ComputeColorHash(preprocessed));
- }
-
- for (var i = 1; i < hashes.Count; i++)
- {
- Assert.Equal(hashes[0], hashes[i]);
- }
-
- _output.WriteLine($"Hash is stable across {hashes.Count} runs");
- }
-
- [Fact]
- public void HashVariance_DifferentImagesProduceDifferentHashes()
- {
- var imageFiles = GetImageFiles(TestImagesDir).Take(20).ToList();
- if (imageFiles.Count < 2)
- {
- _output.WriteLine("Not enough test images for variance test");
- return;
- }
-
- var hashDict = new Dictionary();
-
- foreach (var file in imageFiles)
- {
- try
- {
- using var bitmap = SKBitmap.Decode(file);
- if (bitmap == null) continue;
-
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- var hash = PerceptualHash.ComputeColorHash(preprocessed);
- hashDict[file] = hash;
- }
- catch
- {
- }
- }
-
- var collisions = 0;
- var comparisons = 0;
- var files = hashDict.Keys.ToList();
-
- for (var i = 0; i < files.Count; i++)
- {
- for (var j = i + 1; j < files.Count; j++)
- {
- var distance = PerceptualHash.HammingDistance(hashDict[files[i]], hashDict[files[j]]);
- comparisons++;
-
- if (distance < 5)
- {
- collisions++;
- _output.WriteLine($"Near collision (distance={distance}): {Path.GetFileName(files[i])} vs {Path.GetFileName(files[j])}");
- }
- }
- }
-
- _output.WriteLine($"Checked {comparisons} pairs, found {collisions} near-collisions");
- }
-
- private static List GetImageFiles(string directory)
- {
- var extensions = new[] { ".jpg", ".jpeg", ".png", ".webp", ".bmp" };
-
- return Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
- .Where(f => extensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
- .ToList();
- }
-}
diff --git a/tools/DbGenerator/DbGenerator.csproj b/tools/DbGenerator/DbGenerator.csproj
deleted file mode 100644
index 0b7c3bc..0000000
--- a/tools/DbGenerator/DbGenerator.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- Exe
- net10.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tools/DbGenerator/GenerateCommand.cs b/tools/DbGenerator/GenerateCommand.cs
deleted file mode 100644
index 2c9cfb5..0000000
--- a/tools/DbGenerator/GenerateCommand.cs
+++ /dev/null
@@ -1,495 +0,0 @@
-using System.ComponentModel;
-using Scry.Core.Data;
-using Scry.Core.Imaging;
-using Scry.Core.Models;
-using Scry.Core.Scryfall;
-using SkiaSharp;
-using Spectre.Console;
-using Spectre.Console.Cli;
-
-namespace DbGenerator;
-
-public sealed class GenerateSettings : CommandSettings
-{
- [CommandArgument(0, "[output]")]
- [Description("Output database file path")]
- [DefaultValue("card_hashes.db")]
- public string Output { get; set; } = "card_hashes.db";
-
- [CommandOption("-c|--count")]
- [Description("Maximum number of cards to include")]
- [DefaultValue(500)]
- public int Count { get; set; } = 500;
-
- [CommandOption("--include-test-cards")]
- [Description("Include priority test cards (default: true)")]
- [DefaultValue(true)]
- public bool IncludeTestCards { get; set; } = true;
-
- [CommandOption("--no-test-cards")]
- [Description("Exclude priority test cards")]
- public bool NoTestCards { get; set; }
-
- [CommandOption("-f|--force")]
- [Description("Force rebuild from scratch")]
- public bool Force { get; set; }
-}
-
-public sealed class GenerateCommand : AsyncCommand
-{
- // Cards that should be included for testing with preferred sets
- private static readonly Dictionary PriorityCardsWithSets = new(StringComparer.OrdinalIgnoreCase)
- {
- // From reference_alpha/ - prefer LEA (Alpha) or LEB (Beta) for classic look
- ["Ancestral Recall"] = ["lea", "leb"],
- ["Badlands"] = ["lea", "leb"],
- ["Balance"] = ["lea", "leb"],
- ["Bayou"] = ["lea", "leb"],
- ["Birds of Paradise"] = ["lea", "leb"],
- ["Black Lotus"] = ["lea", "leb"],
- ["Channel"] = ["lea", "leb"],
- ["Chaos Orb"] = ["lea", "leb"],
- ["Clone"] = ["lea", "leb"],
- ["Control Magic"] = ["lea", "leb"],
- ["Counterspell"] = ["lea", "leb"],
- ["Dark Ritual"] = ["lea", "leb"],
- ["Demonic Tutor"] = ["lea", "leb"],
- ["Disenchant"] = ["lea", "leb"],
- ["Fireball"] = ["lea", "leb"],
- ["Force of Nature"] = ["lea", "leb"],
- ["Fork"] = ["lea", "leb"],
- ["Giant Growth"] = ["lea", "leb"],
- ["Hypnotic Specter"] = ["lea", "leb"],
- ["Lightning Bolt"] = ["lea", "leb"],
- ["Llanowar Elves"] = ["lea", "leb"],
- ["Mahamoti Djinn"] = ["lea", "leb"],
- ["Mind Twist"] = ["lea", "leb"],
- ["Mox Emerald"] = ["lea", "leb"],
- ["Mox Jet"] = ["lea", "leb"],
- ["Mox Pearl"] = ["lea", "leb"],
- ["Mox Ruby"] = ["lea", "leb"],
- ["Mox Sapphire"] = ["lea", "leb"],
- ["Nightmare"] = ["lea", "leb"],
- ["Plateau"] = ["lea", "leb"],
- ["Regrowth"] = ["lea", "leb"],
- ["Rock Hydra"] = ["lea", "leb"],
- ["Royal Assassin"] = ["lea", "leb"],
- ["Savannah"] = ["lea", "leb"],
- ["Scrubland"] = ["lea", "leb"],
- ["Serra Angel"] = ["lea", "leb"],
- ["Shivan Dragon"] = ["lea", "leb"],
- ["Sol Ring"] = ["lea", "leb"],
- ["Swords to Plowshares"] = ["lea", "leb"],
- ["Taiga"] = ["lea", "leb"],
- ["Time Walk"] = ["lea", "leb"],
- ["Timetwister"] = ["lea", "leb"],
- ["Tropical Island"] = ["lea", "leb"],
- ["Tundra"] = ["lea", "leb"],
- ["Underground Sea"] = ["lea", "leb"],
- ["Wheel of Fortune"] = ["lea", "leb"],
- ["Wrath of God"] = ["lea", "leb"],
-
- // From reference/ - any set is fine
- ["Brainstorm"] = [],
- ["Force of Will"] = [],
- ["Griselbrand"] = [],
- ["Lotus Petal"] = [],
- ["Ponder"] = [],
- ["Show and Tell"] = [],
- ["Volcanic Island"] = [],
- ["Wasteland"] = [],
-
- // From single_cards/ - any set is fine
- ["Adanto Vanguard"] = [],
- ["Angel of Sanctions"] = [],
- ["Attunement"] = [],
- ["Avaricious Dragon"] = [],
- ["Burgeoning"] = [],
- ["Jarad, Golgari Lich Lord"] = [],
- ["Meletis Charlatan"] = [],
- ["Mindstab Thrull"] = [],
- ["Pacifism"] = [],
- ["Platinum Angel"] = [],
- ["Queen Marchesa"] = [],
- ["Spellseeker"] = [],
- ["Tarmogoyf"] = [],
- ["Thought Reflection"] = [],
- ["Unsummon"] = [],
-
- // From varying_quality - prefer older sets
- ["Dragon Whelp"] = ["lea", "leb"],
- ["Evil Eye of Orms-by-Gore"] = [],
- ["Instill Energy"] = ["lea", "leb"],
-
- // Popular cards for general testing
- ["Lightning Helix"] = [],
- ["Path to Exile"] = [],
- ["Thoughtseize"] = [],
- ["Fatal Push"] = [],
- ["Snapcaster Mage"] = [],
- ["Jace, the Mind Sculptor"] = [],
- ["Liliana of the Veil"] = [],
- ["Noble Hierarch"] = [],
- ["Goblin Guide"] = [],
- ["Eidolon of the Great Revel"] = [],
- };
-
- public override async Task ExecuteAsync(CommandContext context, GenerateSettings settings)
- {
- var outputDb = settings.Output;
- var maxCards = settings.Count;
- var includeTestCards = settings.IncludeTestCards && !settings.NoTestCards;
- var forceRebuild = settings.Force;
-
- // Header
- AnsiConsole.Write(new FigletText("Scry DB Gen").Color(Color.Blue));
-
- var configTable = new Table()
- .Border(TableBorder.Rounded)
- .AddColumn("Setting")
- .AddColumn("Value");
-
- configTable.AddRow("Output", outputDb);
- configTable.AddRow("Max Cards", maxCards.ToString());
- configTable.AddRow("Test Cards", includeTestCards ? "[green]Yes[/]" : "[grey]No[/]");
- configTable.AddRow("Force Rebuild", forceRebuild ? "[yellow]Yes[/]" : "[grey]No[/]");
-
- AnsiConsole.Write(configTable);
- AnsiConsole.WriteLine();
-
- var priorityCards = new HashSet(PriorityCardsWithSets.Keys, StringComparer.OrdinalIgnoreCase);
-
- // Force rebuild if requested
- if (forceRebuild && File.Exists(outputDb))
- {
- AnsiConsole.MarkupLine("[yellow]Force rebuild requested, removing existing database...[/]");
- File.Delete(outputDb);
- }
-
- using var httpClient = new HttpClient();
- httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Scry/1.0 (MTG Card Scanner - Database Generator)");
-
- using var scryfallClient = new ScryfallClient(httpClient);
- using var db = new CardDatabase(outputDb);
-
- // Check existing database state
- var existingCardIds = await db.GetExistingCardIdsAsync();
- var existingCardNames = await db.GetExistingCardNamesAsync();
- var existingOracleIds = await db.GetExistingOracleIdsAsync();
- var existingSetIds = await db.GetExistingSetIdsAsync();
- var existingCount = await db.GetCardCountAsync();
- var storedScryfallDate = await db.GetMetadataAsync("scryfall_updated_at");
-
- var dbStateTable = new Table()
- .Border(TableBorder.Rounded)
- .Title("[blue]Current Database State[/]")
- .AddColumn("Metric")
- .AddColumn("Count", c => c.RightAligned());
-
- dbStateTable.AddRow("Cards", existingCount.ToString());
- dbStateTable.AddRow("Oracles", existingOracleIds.Count.ToString());
- dbStateTable.AddRow("Sets", existingSetIds.Count.ToString());
-
- AnsiConsole.Write(dbStateTable);
- AnsiConsole.WriteLine();
-
- // Fetch all sets
- List scryfallSets = [];
- await AnsiConsole.Status()
- .Spinner(Spinner.Known.Dots)
- .StartAsync("Fetching sets from Scryfall...", async ctx =>
- {
- scryfallSets = await scryfallClient.GetAllSetsAsync();
- });
-
- AnsiConsole.MarkupLine($"[green]✓[/] Found [blue]{scryfallSets.Count}[/] sets");
-
- var setsById = scryfallSets.ToDictionary(s => s.Id ?? "", s => s);
- var setsByCode = scryfallSets.ToDictionary(s => s.Code ?? "", s => s, StringComparer.OrdinalIgnoreCase);
-
- // Insert any new sets
- var newSets = scryfallSets
- .Where(s => s.Id != null && !existingSetIds.Contains(s.Id))
- .Select(s => s.ToSet())
- .ToList();
-
- if (newSets.Count > 0)
- {
- AnsiConsole.MarkupLine($"[green]✓[/] Inserting [blue]{newSets.Count}[/] new sets");
- await db.InsertSetBatchAsync(newSets);
- }
-
- // Fetch bulk data info
- BulkDataInfo? bulkInfo = null;
- await AnsiConsole.Status()
- .Spinner(Spinner.Known.Dots)
- .StartAsync("Fetching bulk data info...", async ctx =>
- {
- bulkInfo = await scryfallClient.GetBulkDataInfoAsync("unique_artwork");
- });
-
- if (bulkInfo?.DownloadUri == null)
- {
- AnsiConsole.MarkupLine("[red]✗ Failed to get bulk data info from Scryfall[/]");
- return 1;
- }
-
- AnsiConsole.MarkupLine($"[green]✓[/] Scryfall data last updated: [blue]{bulkInfo.UpdatedAt:yyyy-MM-dd HH:mm}[/]");
-
- // Check if we need to update at all
- var scryfallDateStr = bulkInfo.UpdatedAt?.ToString("O") ?? "";
- var needsUpdate = existingCount == 0 ||
- storedScryfallDate != scryfallDateStr ||
- existingCount < maxCards;
-
- // Also check if all priority cards exist
- var missingPriorityCards = includeTestCards
- ? priorityCards.Where(c => !existingCardNames.Contains(c)).ToList()
- : [];
-
- if (missingPriorityCards is not [])
- {
- AnsiConsole.MarkupLine($"[yellow]![/] Missing [blue]{missingPriorityCards.Count}[/] priority cards");
- needsUpdate = true;
- }
-
- if (!needsUpdate)
- {
- AnsiConsole.MarkupLine("[green]✓ Database is up-to-date, no changes needed[/]");
- return 0;
- }
-
- AnsiConsole.WriteLine();
-
- var newCards = new List();
- var newOracles = new Dictionary();
- var processed = 0;
- var errors = 0;
- var skipped = 0;
- var priorityFound = 0;
- var priorityNeeded = includeTestCards ? priorityCards.Count : 0;
-
- // Track which priority cards we've already found with their set
- var foundPriorityWithSet = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- // Helper to check if a set is preferred for a priority card
- static bool IsPreferredSet(string cardName, string setCode)
- {
- if (!PriorityCardsWithSets.TryGetValue(cardName, out var preferredSets))
- return false;
-
- return preferredSets.Length == 0 || preferredSets.Contains(setCode, StringComparer.OrdinalIgnoreCase);
- }
-
- await AnsiConsole.Progress()
- .AutoClear(false)
- .HideCompleted(false)
- .Columns(
- new RemainingTimeColumn(),
- new SpinnerColumn(),
- new ProgressBarColumn(),
- new PercentageColumn(),
- new TaskDescriptionColumn()
- {
- Alignment = Justify.Left,
- }
- )
- .StartAsync(async ctx =>
- {
- var downloadTask = ctx.AddTask("[blue]Downloading & processing cards[/]", maxValue: maxCards);
- var priorityTask = ctx.AddTask("[green]Priority cards[/]", maxValue: priorityNeeded);
-
- await foreach (var scryfallCard in scryfallClient.StreamBulkDataAsync(bulkInfo.DownloadUri))
- {
- // Skip non-English cards
- if (scryfallCard.Lang != "en")
- continue;
-
- var imageUri = scryfallCard.GetImageUri("normal");
- if (string.IsNullOrEmpty(imageUri))
- continue;
-
- var cardId = scryfallCard.Id ?? Guid.NewGuid().ToString();
- var cardName = scryfallCard.Name ?? "Unknown";
- var setCode = scryfallCard.Set ?? "???";
- var oracleId = scryfallCard.OracleId ?? cardId;
- var setId = scryfallCard.SetId ?? "";
-
- // Check if this card already exists in the database
- if (existingCardIds.Contains(cardId))
- {
- skipped++;
- continue;
- }
-
- // Check if this is a priority card we might need
- var isPriorityCard = includeTestCards && priorityCards.Contains(cardName);
- var isPreferred = isPriorityCard && IsPreferredSet(cardName, setCode);
-
- // If this priority card already found with preferred set, skip
- if (isPriorityCard && foundPriorityWithSet.TryGetValue(cardName, out var existingSet))
- {
- if (IsPreferredSet(cardName, existingSet))
- continue;
- if (!isPreferred)
- continue;
- }
-
- // Calculate how many slots we have left
- var totalCards = existingCount + newCards.Count;
- var priorityRemaining = priorityNeeded - foundPriorityWithSet.Count;
- var slotsForNonPriority = maxCards - priorityRemaining;
-
- // Skip if we have enough non-priority cards and this isn't priority
- if (!isPriorityCard && totalCards >= slotsForNonPriority)
- continue;
-
- // Download and process image
- try
- {
- downloadTask.Description = $"[blue]{Markup.Escape(cardName.Length > 30 ? cardName[..27] + "..." : cardName)}[/]";
-
- var imageBytes = await httpClient.GetByteArrayAsync(imageUri);
- using var bitmap = SKBitmap.Decode(imageBytes);
-
- if (bitmap == null)
- {
- errors++;
- continue;
- }
-
- // Apply CLAHE preprocessing and compute hash
- using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
- var hash = PerceptualHash.ComputeColorHash(preprocessed);
-
- // Create Card (printing) with hash
- var card = scryfallCard.ToCard() with { Hash = hash };
- newCards.Add(card);
-
- // Track Oracle if we haven't seen it
- if (!existingOracleIds.Contains(oracleId) && !newOracles.ContainsKey(oracleId))
- {
- newOracles[oracleId] = scryfallCard.ToOracle();
- }
-
- if (isPriorityCard)
- {
- foundPriorityWithSet[cardName] = setCode;
- priorityFound++;
- priorityTask.Increment(1);
- }
-
- processed++;
- downloadTask.Increment(1);
-
- // Check if we have enough cards
- var foundAllPriority = foundPriorityWithSet.Count >= priorityNeeded;
- if (existingCount + newCards.Count >= maxCards && foundAllPriority)
- break;
-
- // Rate limit to be nice to Scryfall
- await Task.Delay(50);
- }
- catch
- {
- errors++;
- }
- }
-
- downloadTask.Value = downloadTask.MaxValue;
- priorityTask.Value = priorityTask.MaxValue;
- });
-
- AnsiConsole.WriteLine();
-
- // Summary table
- var summaryTable = new Table()
- .Border(TableBorder.Rounded)
- .Title("[blue]Processing Summary[/]")
- .AddColumn("Metric")
- .AddColumn("Count", c => c.RightAligned());
-
- summaryTable.AddRow("Skipped (already in DB)", skipped.ToString());
- summaryTable.AddRow("Newly processed", $"[green]{processed}[/]");
- summaryTable.AddRow("New oracles", newOracles.Count.ToString());
- summaryTable.AddRow("Priority cards found", $"{priorityFound}/{priorityNeeded}");
- summaryTable.AddRow("Errors", errors > 0 ? $"[red]{errors}[/]" : "0");
-
- AnsiConsole.Write(summaryTable);
- AnsiConsole.WriteLine();
-
- // Insert oracles first (cards reference them)
- if (newOracles.Count > 0)
- {
- await AnsiConsole.Status()
- .Spinner(Spinner.Known.Dots)
- .StartAsync($"Inserting {newOracles.Count} new oracles...", async ctx =>
- {
- await db.InsertOracleBatchAsync(newOracles.Values);
- });
- AnsiConsole.MarkupLine($"[green]✓[/] Inserted [blue]{newOracles.Count}[/] oracles");
- }
-
- if (newCards.Count > 0)
- {
- await AnsiConsole.Status()
- .Spinner(Spinner.Known.Dots)
- .StartAsync($"Inserting {newCards.Count} new cards...", async ctx =>
- {
- await db.InsertCardBatchAsync(newCards);
- });
- AnsiConsole.MarkupLine($"[green]✓[/] Inserted [blue]{newCards.Count}[/] cards");
- }
-
- await db.SetMetadataAsync("generated_at", DateTime.UtcNow.ToString("O"));
- await db.SetMetadataAsync("scryfall_updated_at", scryfallDateStr);
-
- var finalCardCount = await db.GetCardCountAsync();
- var finalOracleCount = await db.GetOracleCountAsync();
- var finalSetCount = await db.GetSetCountAsync();
-
- await db.SetMetadataAsync("card_count", finalCardCount.ToString());
- await db.SetMetadataAsync("oracle_count", finalOracleCount.ToString());
- await db.SetMetadataAsync("set_count", finalSetCount.ToString());
-
- AnsiConsole.WriteLine();
-
- var finalTable = new Table()
- .Border(TableBorder.Double)
- .Title("[green]Final Database State[/]")
- .AddColumn("Metric")
- .AddColumn("Count", c => c.RightAligned());
-
- finalTable.AddRow("Cards", $"[green]{finalCardCount}[/]");
- finalTable.AddRow("Oracles", $"[green]{finalOracleCount}[/]");
- finalTable.AddRow("Sets", $"[green]{finalSetCount}[/]");
- finalTable.AddRow("Output", $"[blue]{outputDb}[/]");
-
- AnsiConsole.Write(finalTable);
-
- // Report missing priority cards
- if (includeTestCards)
- {
- var missing = priorityCards.Where(c => !foundPriorityWithSet.ContainsKey(c)).ToList();
-
- if (missing.Count > 0)
- {
- AnsiConsole.WriteLine();
- AnsiConsole.MarkupLine($"[yellow]Missing priority cards ({missing.Count}):[/]");
-
- var tree = new Tree("[yellow]Missing Cards[/]");
- foreach (var name in missing.Take(20))
- {
- tree.AddNode($"[grey]{Markup.Escape(name)}[/]");
- }
- if (missing.Count > 20)
- {
- tree.AddNode($"[grey]... and {missing.Count - 20} more[/]");
- }
- AnsiConsole.Write(tree);
- }
- }
-
- return 0;
- }
-}
diff --git a/tools/DbGenerator/Program.cs b/tools/DbGenerator/Program.cs
deleted file mode 100644
index 99d841a..0000000
--- a/tools/DbGenerator/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using DbGenerator;
-using Spectre.Console.Cli;
-
-var app = new CommandApp();
-
-app.Configure(config =>
-{
- config.SetApplicationName("dbgen");
- config.SetApplicationVersion("1.0.0");
-});
-
-return await app.RunAsync(args);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..909e901
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "expo/tsconfig.base",
+ "compilerOptions": {
+ "strict": true,
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ ".expo/types/**/*.ts",
+ "expo-env.d.ts"
+ ]
+}