diff --git a/.gitignore b/.gitignore index a8b0d1d..0c2c5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,44 @@ -# ---> Android -# Gradle files -.gradle/ -build/ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files -# Local configuration file (sdk path, etc) -local.properties +# dependencies +node_modules/ -# Log/OS Files -*.log +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -*.apk -output.json - -# IntelliJ -*.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files +# Native +.kotlin/ +*.orig.* *.jks -*.keystore +*.p8 +*.p12 +*.key +*.mobileprovision -# Google Services (e.g. APIs or Firebase) -google-services.json +# Metro +.metro-health-check* -# Android Profiling -*.hprof +# debug +npm-debug.* +yarn-debug.* +yarn-error.* +# macOS +.DS_Store +*.pem + +# local env files +.env +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +# generated native folders +/ios +/android diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b7ed837 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1 @@ +{ "recommendations": ["expo.vscode-expo-tools"] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e2798e4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit", + "source.sortMembers": "explicit" + } +} diff --git a/README.md b/README.md index bb1f63d..c1a4e02 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,106 @@ -# nextgenmobile +# Atomic Habits AI - Habit Tracker & Personal Library -This is the starting point for Mobile App Development for NextGen \ No newline at end of file +Atomic Habits AI is a cross-platform mobile and web application designed to help users build lasting habits through a science-backed tracking system and a personalized AI-powered library. It combines local-first performance with cloud synchronization and AI insights. + +--- + +## 🏗 Architecture + +The application follows a **Local-First, Cloud-Sync** architecture. + +### Frontend +- **Framework:** [Expo](https://expo.dev/) (React Native) with [Expo Router](https://docs.expo.dev/router/introduction/) for file-based routing. +- **Language:** TypeScript. +- **State Management:** Local SQLite database for offline-first capabilities, synced with Supabase. +- **UI/UX:** Custom design system built with `Vanilla CSS` (via React Native Stylesheets), utilizing `Lucide React Native` for iconography and `Reanimated` for smooth transitions. + +### Backend & Cloud +- **Platform:** [Supabase](https://supabase.com/) +- **Authentication:** Supabase Auth (Email/Password, JWT). +- **Database:** PostgreSQL (Cloud) + SQLite (Local). +- **Storage:** Supabase Storage for PDF/Epub books. +- **Edge Functions:** Supabase Edge Functions (Deno) for AI processing and book parsing. + +### AI Integration +- **Models:** Gemini (via Supabase Edge Functions). +- **Features:** Automated book metadata extraction, AI-generated reading insights/synthesis, and interactive habit coaching. + +--- + +## 🔐 Security + +Security is integrated at every layer of the stack: + +1. **Authentication:** Secure session management using `AsyncStorage` and Supabase JWTs. +2. **Row Level Security (RLS):** Supabase RLS policies ensure users can only access their own habits, books, and logs. +3. **Secure Storage:** sensitive files are accessed via **Signed URLs** with limited expiration windows (1 hour) rather than public links. +4. **Local Encryption:** (Optional/Roadmap) Local SQLite data protection. +5. **Environment Variables:** All sensitive keys (Supabase URL, Anon Key) are managed via `.env` files and `EXPO_PUBLIC_` prefixes. + +--- + +## 🗄 Database Schema + +The system uses a mirrored schema between local SQLite and cloud PostgreSQL. + +### Core Tables: +- **`books`**: Stores library items (`id`, `user_id`, `title`, `author`, `file_uri`, `current_page`, `status`). +- **`habits`**: Core habit tracking logic (`id`, `user_id`, `name`, `frequency`, `streak`, `last_completed`). +- **`reading_logs`**: Tracks progress over time (`id`, `book_id`, `duration_seconds`, `pages_read`). +- **`chat_history`**: Persists AI interactions for context-aware coaching. + +--- + +## 🛠 Running the Project + +### Prerequisites +- Node.js (v18+) +- Expo Go app (for physical device testing) or Android Studio / Xcode (for emulators). + +### Installation +```bash +# Clone the repository +git clone +cd atomichabitsai + +# Install dependencies +npm install +``` + +### Development +```bash +# Start the Expo development server +npx expo start +``` + +- Press **`a`** for Android emulator. +- Press **`i`** for iOS simulator. +- Press **`w`** for web. + +--- + +## 🐞 Debugging Details + +### Logging +The application uses structured logs for critical paths: +- **`[PdfReader]`**: WebView lifecycle and file writing logs. +- **`[downloadBook]`**: Sync and storage fetch status. +- **`[resolveFileUri]`**: Path resolution debugging for iOS container changes. + +### Common Troubleshooting +1. **Stale iOS Paths:** If files don't open on iOS after an update, the `resolveFileUri` utility automatically corrects absolute paths that contain outdated container UUIDs. +2. **Sync Failures:** Check the `performMutation` calls in `sync.ts`. Ensure the device has internet access for Supabase connectivity. +3. **PDF Reader Crashes:** Ensure the library `@bildau/rn-pdf-reader` is correctly patched via `patch-package` to handle the Legacy FileSystem API in Expo 54. + +--- + +## 🧑‍💻 Backend Developer Notes + +### Edge Functions +Located in `/supabase/functions/`: +- **`process-book-ai`**: Parses uploaded filenames to suggest titles/authors and generates reading summaries. +- **`chat-ai`**: The core engine for the AI habit coach. + +### Storage +- Bucket name: `books` +- Access: Private (accessed via `createSignedUrl` in `src/lib/file-utils.ts`). diff --git a/app.json b/app.json new file mode 100644 index 0000000..1eb324d --- /dev/null +++ b/app.json @@ -0,0 +1,68 @@ +{ + "expo": { + "name": "RCS BatsirAI", + "slug": "atomichabitsai", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "atomichabitsai", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "ios": { + "supportsTablet": true, + "bundleIdentifier": "com.phaseofficial.atomichabitsai", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } + }, + "android": { + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false, + "package": "com.phaseofficial.atomichabitsai" + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + [ + "expo-router", + { + "origin": "https://atomichabits.ai/", + "root": "src/app" + } + ], + [ + "expo-splash-screen", + { + "image": "./assets/images/favicon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "backgroundColor": "#000000" + } + } + ] + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + }, + "extra": { + "router": { + "origin": "https://atomichabits.ai/", + "root": "src/app" + }, + "eas": { + "projectId": "5f2929a1-ae47-4616-a893-8c7161eaafe9" + } + } + } +} diff --git a/assets/images/Artboard 1 logo.png b/assets/images/Artboard 1 logo.png new file mode 100644 index 0000000..10c69b1 Binary files /dev/null and b/assets/images/Artboard 1 logo.png differ diff --git a/assets/images/android-icon-background.png b/assets/images/android-icon-background.png new file mode 100644 index 0000000..5ffefc5 Binary files /dev/null and b/assets/images/android-icon-background.png differ diff --git a/assets/images/android-icon-foreground.png b/assets/images/android-icon-foreground.png new file mode 100644 index 0000000..52d717c Binary files /dev/null and b/assets/images/android-icon-foreground.png differ diff --git a/assets/images/android-icon-monochrome.png b/assets/images/android-icon-monochrome.png new file mode 100644 index 0000000..77484eb Binary files /dev/null and b/assets/images/android-icon-monochrome.png differ diff --git a/assets/images/calendar_design.png b/assets/images/calendar_design.png new file mode 100644 index 0000000..7ff7513 Binary files /dev/null and b/assets/images/calendar_design.png differ diff --git a/assets/images/dashboard_design.png b/assets/images/dashboard_design.png new file mode 100644 index 0000000..f5e3479 Binary files /dev/null and b/assets/images/dashboard_design.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..cf1f05b Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/habits_design.png b/assets/images/habits_design.png new file mode 100644 index 0000000..39e2964 Binary files /dev/null and b/assets/images/habits_design.png differ diff --git a/assets/images/icon.png b/assets/images/icon.png new file mode 100644 index 0000000..52d717c Binary files /dev/null and b/assets/images/icon.png differ diff --git a/assets/images/partial-react-logo.png b/assets/images/partial-react-logo.png new file mode 100644 index 0000000..66fd957 Binary files /dev/null and b/assets/images/partial-react-logo.png differ diff --git a/assets/images/react-logo.png b/assets/images/react-logo.png new file mode 100644 index 0000000..9d72a9f Binary files /dev/null and b/assets/images/react-logo.png differ diff --git a/assets/images/react-logo@2x.png b/assets/images/react-logo@2x.png new file mode 100644 index 0000000..2229b13 Binary files /dev/null and b/assets/images/react-logo@2x.png differ diff --git a/assets/images/react-logo@3x.png b/assets/images/react-logo@3x.png new file mode 100644 index 0000000..a99b203 Binary files /dev/null and b/assets/images/react-logo@3x.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/assets/ui/mobile/ai.html b/assets/ui/mobile/ai.html new file mode 100644 index 0000000..23e6a20 --- /dev/null +++ b/assets/ui/mobile/ai.html @@ -0,0 +1,230 @@ + + + + + +Serene Path | AI Assistant + + + + + + + + + + +
+
+
+user profile picture +
+

Serene Path

+
+
+ +
+
+
+ +
+
+
+ + +
+AI is syncing your plan... +
+
+ +
+ +
+
+Serene AI +10:02 AM +
+
+

+ Good morning. I've analyzed your sleep data and upcoming tasks. You seem to have a productive window between 10 AM and 12 PM. Would you like me to block this time for your deep work session? +

+
+
+ +
+
+10:05 AM +You +
+
+

+ Yes, please. Also, can you check if I have any reading assignments for tonight's book club? +

+
+
+ +
+
+Serene AI +10:05 AM +
+
+

+ Of course. I've scheduled your deep work block. Regarding your book club: you have to finish Chapter 4 of "The Art of Stillness". +

+
+
+book cover +
+
+

Chapter 4: The Inner Journey

+

12 pages remaining • 15 min read

+
+
+
+
+ +
+
+
+ +
+
+ +
+ + + +
+ +
+
+
+ + + +
+
+
+ + +
+ \ No newline at end of file diff --git a/assets/ui/mobile/ai.png b/assets/ui/mobile/ai.png new file mode 100644 index 0000000..03ace80 Binary files /dev/null and b/assets/ui/mobile/ai.png differ diff --git a/assets/ui/mobile/calendar.html b/assets/ui/mobile/calendar.html new file mode 100644 index 0000000..4d2f8d6 --- /dev/null +++ b/assets/ui/mobile/calendar.html @@ -0,0 +1,267 @@ + + + + + +Serene Path - Daily Plan + + + + + + + + + + +
+
+
+ +
+

Serene Path

+
+
+ +
+
+
+ +
+
+

Tuesday

+

Oct 24

+
+
+ + +
+
+ +
+ +
+
08:00
+
09:00
+
10:00
+
11:00
+
12:00
+
13:00
+
14:00
+
15:00
+
16:00
+
17:00
+
18:00
+
19:00
+
20:00
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+

Sleep Cycle

+favorite +
+
+Health Data • 7h 20m +
+
+ +
+
+
+light_mode +
+
+

Morning Routine

+

Meditation & Journaling

+
+
+09:30 - 10:15 +
+ +
+
+
+

Deep Work

+ +
+

Focusing on the Product Strategy Deck and Quarter 4 planning. Phone in DND mode.

+
+
+Focus Mode +
+10:45 - 12:45 +
+
+ +
+
+restaurant +

Lunch Break

+
+13:00 - 13:45 +
+ +
+
+
+ + +
+
+

Creative Review

+

With Design Team

+
+
+14:00 - 15:00 +
+ +
+
+directions_walk +

Mindful Walk

+
+16:30 - 17:15 +
+ +
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/assets/ui/mobile/calendar.png b/assets/ui/mobile/calendar.png new file mode 100644 index 0000000..8eb7581 Binary files /dev/null and b/assets/ui/mobile/calendar.png differ diff --git a/assets/ui/mobile/dashboard.html b/assets/ui/mobile/dashboard.html new file mode 100644 index 0000000..0bb2ec3 --- /dev/null +++ b/assets/ui/mobile/dashboard.html @@ -0,0 +1,239 @@ + + + + + + Serene Path + + + + +
+
+ + settings +
+ +
+
GOOD MORNING, ALEX
+
Today is a fresh canvas. Take a deep breath.
+
+ +
Today's Focus 9:30 AM — 11:00 AM
+
+
DEEP WORK: INTERFACE REFINEMENT
+
Focus on the typography hierarchy and tonal layering transitions for the mobile navigation shell.
+
+ + +
+
+ +
Reading Progress
+
+
+
Building a Second Brain
+
Tiago Forte • 42 pages left
+
+
65%
+
+ +
KEYSTONE HABITS
+
2/3 Done
+
+ check_circle + Morning Meditation +
+
+ check_circle + Deep Work Session +
+
+ radio_button_unchecked + 20min Evening Walk +
+ +
ENERGY CHECK-IN
+
How are you feeling right now? Listen to your body.
+
+
Drained
+
Balanced
+
Radiant
+
+ +
MANAGEMENT DECK
+
+
ads_click
Google Ads
+
social_leaderboard
Facebook
+
analytics
Analytics
+
mail
Inbox
+
payments
Finance
+
add
Add Tool
+
+ + +
+ + \ No newline at end of file diff --git a/assets/ui/mobile/dashboard.png b/assets/ui/mobile/dashboard.png new file mode 100644 index 0000000..edb1153 Binary files /dev/null and b/assets/ui/mobile/dashboard.png differ diff --git a/assets/ui/mobile/habits.html b/assets/ui/mobile/habits.html new file mode 100644 index 0000000..e85fc9e --- /dev/null +++ b/assets/ui/mobile/habits.html @@ -0,0 +1,323 @@ + + + + + +Serene Path | Habit & Identity Tracker + + + + + + + + + +
+
+
+ +
+Serene Path +
+
+ +
+
+
+ +
+
+
+Foundation +

Who I am becoming

+

Focusing on identity-based growth rather than just checking boxes.

+
+ +
+ +
+
+
+Primary Identity +

"I am a person who prioritizes deep knowledge over superficial distraction."

+
+verified +Rooted in 12 consistent days +
+
+
+
+
+
+

Secondary Shift

+

"I am a healthy athlete who treats my body with respect."

+
+
+
+
M
+
T
+
W
+
+Active +
+
+
+
+ +
+
+
+warning +
+
+

Burnout Alert: Momentum is Fading

+

You've missed 4 habits in the last 24 hours. The path to growth requires rhythm, but also recovery.

+
+ +
+
+ +
+ +
+
+

Keystone Habits

+ +
+
+ +
+ +
+
+

Morning Meditation

+Identity: Calm Observer +
+

"I don't react, I observe my thoughts without judgment."

+
+
+

Streak

+

12d

+
+
+ +
+ +
+
+

Read 20 Pages

+Identity: Lifelong Learner +
+

"Knowledge is the compound interest of my identity."

+
+
+

Streak

+

45d

+
+
+ +
+ +
+
+

Evening Reflection

+Identity: Intentional Soul +
+

"I review my day to refine my tomorrow."

+
+
+

Streak

+

0d

+
+
+
+
+ +
+
+

Weekly Rhythm

+
+ +
+
+
+M +
+
+
+T +
+
+
+W +
+
+
+T +
+
+
+F +
+
+
+S +
+
+
+S +
+
+
+
+Weekly Completion +78% +
+
+
+
+
+
+

Identity reinforcement suggests you are 14 days away from these habits feeling "automatic." Keep going.

+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/assets/ui/mobile/habits.png b/assets/ui/mobile/habits.png new file mode 100644 index 0000000..38f578d Binary files /dev/null and b/assets/ui/mobile/habits.png differ diff --git a/assets/ui/mobile/library.html b/assets/ui/mobile/library.html new file mode 100644 index 0000000..5481093 --- /dev/null +++ b/assets/ui/mobile/library.html @@ -0,0 +1,287 @@ + + + + + +Serene Path - Library + + + + + + + + + +
+
+
+ +
+Serene Path +
+ +
+
+ +
+
+
+

Library

+

Curate your mind's expansion

+
+
+
+
+
+Daily Goal +45 / 60 min +
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+auto_stories +Continue Reading +
+

The Architecture of Stillness

+

Discover the profound impact of intentional physical environments on mental clarity and spiritual peace.

+
+
+ +
+
+bookmark +
+
+ +
+
+
+
+
+
+ +
+
+

Your Collection

+ +
+
+ +
+
+ +
+Open PDF +
+
+

Mindful Breathing

+

Dr. Aris Thorne

+
+
+
+
+82% +
+
+ +
+
+ +
+Open PDF +
+
+

Quietude

+

Elena Vance

+
+
+
+
+24% +
+
+ +
+
+ +
+Open PDF +
+
+

Nature's Rythm

+

Marcus Sol

+
+
+
+
+45% +
+
+ +
+
+add_circle +Upload PDF +
+
+
+
+ +
+

Recent Articles

+
+
+
+description +
+
+
Journal of Cognitive Rest
+

Exploring the synaptic benefits of 15-minute digital detoxes.

+
+ +
+
+
+article +
+
+
The Art of Soft Focus
+

Balancing deep work with peripheral awareness for creativity.

+
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/assets/ui/mobile/library.png b/assets/ui/mobile/library.png new file mode 100644 index 0000000..f071f79 Binary files /dev/null and b/assets/ui/mobile/library.png differ diff --git a/assets/ui/mobile/sprint.html b/assets/ui/mobile/sprint.html new file mode 100644 index 0000000..c7b320d --- /dev/null +++ b/assets/ui/mobile/sprint.html @@ -0,0 +1,259 @@ + + + + + +Serene Path | Focus Sprint + + + + + + + + + + +
+
+
+user profile picture +
+

Serene Path

+
+ +
+
+
+ +
+
+ +
+ + + +
+
+

Current Sprint: Deep Work

+

+ 24:59 +

+
+ + + +
+
+
+ +
+
+Daily Goal: 4 Sprints +2 / 4 Completed +
+
+
+
+
+
+ +
+ +
+
+eco +

Focus Strategy

+
+
+
+

Work

+

25 min

+
+
+

Break

+

5 min

+
+ +
+
+ +
+
+

Sprint Tasks

+ +
+
+ +
+ +
+

Refine Brand Identity Guidelines

+

Priority focus for this session

+
+Current +
+ +
+ +
+

Review Q3 Performance Metrics

+
+Analytics +
+
+
+ +
+ +
+

Client Proposal Draft

+
+Sales +
+
+
+
+
+ +
+
+lightbulb +
+

Deep Work Insight

+

+ "Your focus is the most valuable currency you possess. Don't spend it on trivial distractions." +

+
+auto_stories +Strategy: Task Batching +
+
+
+
+
+ + + \ No newline at end of file diff --git a/assets/ui/mobile/sprint.png b/assets/ui/mobile/sprint.png new file mode 100644 index 0000000..a0f5123 Binary files /dev/null and b/assets/ui/mobile/sprint.png differ diff --git a/assets/ui/web/ai.html b/assets/ui/web/ai.html new file mode 100644 index 0000000..456fe7d --- /dev/null +++ b/assets/ui/web/ai.html @@ -0,0 +1,316 @@ + + + + + +Serene Path AI Assistant + + + + + + + + + + +
+ +
+ +
+
+
+smart_toy +
+
+

AI Assistant

+ + Online + +
+
+
+ + +
+
+ +
+ +
+
+smart_toy +
+
+
+

+ Good morning, Elena. I've analyzed your sleep patterns and today's calendar. You have a few open windows this afternoon. Shall we plan a focused reflection session or a nature walk to keep your serenity score high? +

+
+Assistant • 09:12 AM +
+
+ +
+
+Elena +
+
+
+

+ I'd like the nature walk around 3:00 PM. Also, please remind me to finish my meditation log before dinner at 7:00 PM. +

+
+You • 09:14 AM +
+
+ +
+
+smart_toy +
+
+
+

+ Perfect choice. I've updated your schedule for today. +

+
+check_circle +
+

Action Recorded

+

Nature Walk scheduled for 15:00

+
+
+
+Assistant • 09:15 AM +
+
+
+ +
+
+ + + +
+
+ + +
+
+
+ + +
+ \ No newline at end of file diff --git a/assets/ui/web/calendar.html b/assets/ui/web/calendar.html new file mode 100644 index 0000000..9b4ec8a --- /dev/null +++ b/assets/ui/web/calendar.html @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +

September 11 — 17

+ +
+
+ + + +
+
+
+
+
+search + +
+
+ + +
+
+ +
+
+ +
+
+
+MON +11 +
+
+TUE +12 +
+
+WED +13 +
+
+THU +14 +
+
+FRI +15 +
+
+SAT +16 +
+
+SUN +17 +
+
+ +
+
+ +
+
08:00 AM
+
09:00 AM
+
10:00 AM
+
11:00 AM
+
12:00 PM
+
01:00 PM
+
02:00 PM
+
03:00 PM
+
04:00 PM
+
+ +
+
+ +
+
+psychology +

Deep Work

+
+

UI Design Language

+

Focus Zone

+
+
+
+
+ +
+
+spa +

Guided Meditation

+
+
+
+
+
+ +
+

Stakeholder Review

+

Design Sync

+
+
+
+
+
+
+
+
+
+
+
+ + +
+ \ No newline at end of file diff --git a/assets/ui/web/dashboard.html b/assets/ui/web/dashboard.html new file mode 100644 index 0000000..3f75f95 --- /dev/null +++ b/assets/ui/web/dashboard.html @@ -0,0 +1,350 @@ + + + + + +Serene Path | Dashboard + + + + + + + + + + + +
+ +
+ +
+
+

Good Morning, Elena

+

Your path today is clear and focused.

+
+
+
+search +
+
+notifications +account_circle +
+
+
+ +
+ +
+
+
+Current Focus +45 mins remaining +
+

Serene Path UI Refinement

+

Deep work session focused on architectural patterns and spatial hierarchy.

+
+
+ + +
+ +
+decorative +
+ +
+
+

Daily Habits

+
+
+
+mindfulness +Meditation +
+check_circle +
+
+
+water_drop +Hydration +
+radio_button_unchecked +
+
+
+local_library +Reading +
+check_circle +
+
+
+
+
+Total Progress +68% +
+
+
+
+
+
+ +
+
+
+auto_awesome +
+Inspiration +
+
+
+history +
+Recent Logs +
+
+
+menu_book +
+Quick Journal +
+
+
+settings_ethernet +
+Integrations +
+
+
+
+ + +
+ + + \ No newline at end of file diff --git a/assets/ui/web/habits.html b/assets/ui/web/habits.html new file mode 100644 index 0000000..c7ab802 --- /dev/null +++ b/assets/ui/web/habits.html @@ -0,0 +1,347 @@ + + + + + +Serene Path | Habits & Identity + + + + + + + + + + + +
+ +
+ +
+
+

Habit Synthesis

+

Nurturing your daily keystone rituals.

+
+
+
+search + +
+
+notifications +account_circle +
+
+
+ +
+ +
+
+
+Identity Anchor +

"I am a focused creator."

+

Connected to Deep Work habit. You've honored this identity for 14 consecutive days.

+
+
+
+
+82% Mastery +
+
+draw +
+
+Active Goal +

The Mindful Athlete

+

Integrated with Movement rituals. 4/5 weekly milestones achieved.

+
+
M
+
T
+
W
+
T
+
F
+
+
+
+ +
+
+

Keystone Habit Performance

+
+ + +
+
+
+ +
+
+
+self_improvement +
+
+

Morning Stillness

+

15 mins • 07:00 AM

+
+
+
+
+
+
+
+
+
+
+
+
+

12 Day Streak

+

+2 from avg

+
+
+ +
+
+
+menu_book +
+
+

Curated Reading

+

20 pages • Afternoon

+
+
+
+
+
+
+
+
+
+
+
+
+

31 Day Streak

+

Milestone: +3d

+
+
+ +
+
+
+forest +
+
+

Nature Exposure

+

10 mins • Sunlight

+
+
+
+
+
+
+
+
+
+
+
+
+

4 Day Streak

+

In Focus

+
+
+
+
+
+
+ + +
+ \ No newline at end of file diff --git a/assets/ui/web/sprint.html b/assets/ui/web/sprint.html new file mode 100644 index 0000000..8973698 --- /dev/null +++ b/assets/ui/web/sprint.html @@ -0,0 +1,303 @@ + + + + + +Digital Sanctuary | Sprint Planner + + + + + + + + + + + +
+ +
+ +
+
+bolt +

Sprint Planner

+
+
+
+search + +
+
+notifications +account_circle +
+
+
+ +
+
+ + + + + + + + + + +
+24:59 +Deep Focus +
+
+
+ + + +
+
+ +
+
+

Sprint Tasks

+ +
+
+ +
+
+ +
+
+

Audit design system color tokens

+
+High Energy +15 mins +
+
+drag_indicator +
+
+
+
+

Review feedback for navigation shell

+
+Batch: Email +25 mins +
+
+drag_indicator +
+
+
+check +
+
+

Update component JSON schema

+

Completed at 09:45 AM

+
+
+
+
+
+ + +
+ + + \ No newline at end of file diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..73ebf58 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,6 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ["babel-preset-expo"], + }; +}; diff --git a/components/external-link.tsx b/components/external-link.tsx new file mode 100644 index 0000000..883e515 --- /dev/null +++ b/components/external-link.tsx @@ -0,0 +1,25 @@ +import { Href, Link } from 'expo-router'; +import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser'; +import { type ComponentProps } from 'react'; + +type Props = Omit, 'href'> & { href: Href & string }; + +export function ExternalLink({ href, ...rest }: Props) { + return ( + { + if (process.env.EXPO_OS !== 'web') { + // Prevent the default behavior of linking to the default browser on native. + event.preventDefault(); + // Open the link in an in-app browser. + await openBrowserAsync(href, { + presentationStyle: WebBrowserPresentationStyle.AUTOMATIC, + }); + } + }} + /> + ); +} diff --git a/components/haptic-tab.tsx b/components/haptic-tab.tsx new file mode 100644 index 0000000..7f3981c --- /dev/null +++ b/components/haptic-tab.tsx @@ -0,0 +1,18 @@ +import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'; +import { PlatformPressable } from '@react-navigation/elements'; +import * as Haptics from 'expo-haptics'; + +export function HapticTab(props: BottomTabBarButtonProps) { + return ( + { + if (process.env.EXPO_OS === 'ios') { + // Add a soft haptic feedback when pressing down on the tabs. + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + props.onPressIn?.(ev); + }} + /> + ); +} diff --git a/components/hello-wave.tsx b/components/hello-wave.tsx new file mode 100644 index 0000000..5def547 --- /dev/null +++ b/components/hello-wave.tsx @@ -0,0 +1,19 @@ +import Animated from 'react-native-reanimated'; + +export function HelloWave() { + return ( + + 👋 + + ); +} diff --git a/components/parallax-scroll-view.tsx b/components/parallax-scroll-view.tsx new file mode 100644 index 0000000..8f09c18 --- /dev/null +++ b/components/parallax-scroll-view.tsx @@ -0,0 +1,80 @@ +import type { PropsWithChildren, ReactElement } from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { + interpolate, + useAnimatedRef, + useAnimatedStyle, + useScrollOffset, +} from 'react-native-reanimated'; + +import { ThemedView } from '@/components/themed-view'; +import { useColorScheme } from '@/hooks/use-color-scheme'; +import { useThemeColor } from '@/hooks/use-theme-color'; + +const HEADER_HEIGHT = 250; + +type Props = PropsWithChildren<{ + headerImage: ReactElement; + headerBackgroundColor: { dark: string; light: string }; +}>; + +export default function ParallaxScrollView({ + children, + headerImage, + headerBackgroundColor, +}: Props) { + const backgroundColor = useThemeColor({}, 'background'); + const colorScheme = useColorScheme() ?? 'light'; + const scrollRef = useAnimatedRef(); + const scrollOffset = useScrollOffset(scrollRef); + const headerAnimatedStyle = useAnimatedStyle(() => { + 'worklet'; + return { + transform: [ + { + translateY: interpolate( + scrollOffset.value, + [-HEADER_HEIGHT, 0, HEADER_HEIGHT], + [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75] + ), + }, + { + scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), + }, + ], + }; + }); + + return ( + + + {headerImage} + + {children} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + height: HEADER_HEIGHT, + overflow: 'hidden', + }, + content: { + flex: 1, + padding: 32, + gap: 16, + overflow: 'hidden', + }, +}); diff --git a/components/themed-text.tsx b/components/themed-text.tsx new file mode 100644 index 0000000..d79d0a1 --- /dev/null +++ b/components/themed-text.tsx @@ -0,0 +1,60 @@ +import { StyleSheet, Text, type TextProps } from 'react-native'; + +import { useThemeColor } from '@/hooks/use-theme-color'; + +export type ThemedTextProps = TextProps & { + lightColor?: string; + darkColor?: string; + type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'; +}; + +export function ThemedText({ + style, + lightColor, + darkColor, + type = 'default', + ...rest +}: ThemedTextProps) { + const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); + + return ( + + ); +} + +const styles = StyleSheet.create({ + default: { + fontSize: 16, + lineHeight: 24, + }, + defaultSemiBold: { + fontSize: 16, + lineHeight: 24, + fontWeight: '600', + }, + title: { + fontSize: 32, + fontWeight: 'bold', + lineHeight: 32, + }, + subtitle: { + fontSize: 20, + fontWeight: 'bold', + }, + link: { + lineHeight: 30, + fontSize: 16, + color: '#0a7ea4', + }, +}); diff --git a/components/themed-view.tsx b/components/themed-view.tsx new file mode 100644 index 0000000..6f181d8 --- /dev/null +++ b/components/themed-view.tsx @@ -0,0 +1,14 @@ +import { View, type ViewProps } from 'react-native'; + +import { useThemeColor } from '@/hooks/use-theme-color'; + +export type ThemedViewProps = ViewProps & { + lightColor?: string; + darkColor?: string; +}; + +export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) { + const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); + + return ; +} diff --git a/components/ui/collapsible.tsx b/components/ui/collapsible.tsx new file mode 100644 index 0000000..6345fde --- /dev/null +++ b/components/ui/collapsible.tsx @@ -0,0 +1,45 @@ +import { PropsWithChildren, useState } from 'react'; +import { StyleSheet, TouchableOpacity } from 'react-native'; + +import { ThemedText } from '@/components/themed-text'; +import { ThemedView } from '@/components/themed-view'; +import { IconSymbol } from '@/components/ui/icon-symbol'; +import { Colors } from '@/constants/theme'; +import { useColorScheme } from '@/hooks/use-color-scheme'; + +export function Collapsible({ children, title }: PropsWithChildren & { title: string }) { + const [isOpen, setIsOpen] = useState(false); + const theme = useColorScheme() ?? 'light'; + + return ( + + setIsOpen((value) => !value)} + activeOpacity={0.8}> + + + {title} + + {isOpen && {children}} + + ); +} + +const styles = StyleSheet.create({ + heading: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + content: { + marginTop: 6, + marginLeft: 24, + }, +}); diff --git a/components/ui/icon-symbol.ios.tsx b/components/ui/icon-symbol.ios.tsx new file mode 100644 index 0000000..9177f4d --- /dev/null +++ b/components/ui/icon-symbol.ios.tsx @@ -0,0 +1,32 @@ +import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'; +import { StyleProp, ViewStyle } from 'react-native'; + +export function IconSymbol({ + name, + size = 24, + color, + style, + weight = 'regular', +}: { + name: SymbolViewProps['name']; + size?: number; + color: string; + style?: StyleProp; + weight?: SymbolWeight; +}) { + return ( + + ); +} diff --git a/components/ui/icon-symbol.tsx b/components/ui/icon-symbol.tsx new file mode 100644 index 0000000..b7ece6b --- /dev/null +++ b/components/ui/icon-symbol.tsx @@ -0,0 +1,41 @@ +// Fallback for using MaterialIcons on Android and web. + +import MaterialIcons from '@expo/vector-icons/MaterialIcons'; +import { SymbolWeight, SymbolViewProps } from 'expo-symbols'; +import { ComponentProps } from 'react'; +import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'; + +type IconMapping = Record['name']>; +type IconSymbolName = keyof typeof MAPPING; + +/** + * Add your SF Symbols to Material Icons mappings here. + * - see Material Icons in the [Icons Directory](https://icons.expo.fyi). + * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app. + */ +const MAPPING = { + 'house.fill': 'home', + 'paperplane.fill': 'send', + 'chevron.left.forwardslash.chevron.right': 'code', + 'chevron.right': 'chevron-right', +} as IconMapping; + +/** + * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web. + * This ensures a consistent look across platforms, and optimal resource usage. + * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons. + */ +export function IconSymbol({ + name, + size = 24, + color, + style, +}: { + name: IconSymbolName; + size?: number; + color: string | OpaqueColorValue; + style?: StyleProp; + weight?: SymbolWeight; +}) { + return ; +} diff --git a/constants/theme.ts b/constants/theme.ts new file mode 100644 index 0000000..f06facd --- /dev/null +++ b/constants/theme.ts @@ -0,0 +1,53 @@ +/** + * Below are the colors that are used in the app. The colors are defined in the light and dark mode. + * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. + */ + +import { Platform } from 'react-native'; + +const tintColorLight = '#0a7ea4'; +const tintColorDark = '#fff'; + +export const Colors = { + light: { + text: '#11181C', + background: '#fff', + tint: tintColorLight, + icon: '#687076', + tabIconDefault: '#687076', + tabIconSelected: tintColorLight, + }, + dark: { + text: '#ECEDEE', + background: '#151718', + tint: tintColorDark, + icon: '#9BA1A6', + tabIconDefault: '#9BA1A6', + tabIconSelected: tintColorDark, + }, +}; + +export const Fonts = Platform.select({ + ios: { + /** iOS `UIFontDescriptorSystemDesignDefault` */ + sans: 'system-ui', + /** iOS `UIFontDescriptorSystemDesignSerif` */ + serif: 'ui-serif', + /** iOS `UIFontDescriptorSystemDesignRounded` */ + rounded: 'ui-rounded', + /** iOS `UIFontDescriptorSystemDesignMonospaced` */ + mono: 'ui-monospace', + }, + default: { + sans: 'normal', + serif: 'serif', + rounded: 'normal', + mono: 'monospace', + }, + web: { + sans: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", + serif: "Georgia, 'Times New Roman', serif", + rounded: "'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif", + mono: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", + }, +}); diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..4cf26cd --- /dev/null +++ b/eas.json @@ -0,0 +1,24 @@ +{ + "cli": { + "version": ">= 18.12.2", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal", + "android": { + "buildType": "apk" + } + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5025da6 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,10 @@ +// https://docs.expo.dev/guides/using-eslint/ +const { defineConfig } = require('eslint/config'); +const expoConfig = require('eslint-config-expo/flat'); + +module.exports = defineConfig([ + expoConfig, + { + ignores: ['dist/*'], + }, +]); diff --git a/hooks/use-color-scheme.ts b/hooks/use-color-scheme.ts new file mode 100644 index 0000000..17e3c63 --- /dev/null +++ b/hooks/use-color-scheme.ts @@ -0,0 +1 @@ +export { useColorScheme } from 'react-native'; diff --git a/hooks/use-color-scheme.web.ts b/hooks/use-color-scheme.web.ts new file mode 100644 index 0000000..7eb1c1b --- /dev/null +++ b/hooks/use-color-scheme.web.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; +import { useColorScheme as useRNColorScheme } from 'react-native'; + +/** + * To support static rendering, this value needs to be re-calculated on the client side for web + */ +export function useColorScheme() { + const [hasHydrated, setHasHydrated] = useState(false); + + useEffect(() => { + setHasHydrated(true); + }, []); + + const colorScheme = useRNColorScheme(); + + if (hasHydrated) { + return colorScheme; + } + + return 'light'; +} diff --git a/hooks/use-theme-color.ts b/hooks/use-theme-color.ts new file mode 100644 index 0000000..0cbc3a6 --- /dev/null +++ b/hooks/use-theme-color.ts @@ -0,0 +1,21 @@ +/** + * Learn more about light and dark modes: + * https://docs.expo.dev/guides/color-schemes/ + */ + +import { Colors } from '@/constants/theme'; +import { useColorScheme } from '@/hooks/use-color-scheme'; + +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]; + } +} diff --git a/metro.config.js b/metro.config.js new file mode 100644 index 0000000..a2c6c3e --- /dev/null +++ b/metro.config.js @@ -0,0 +1,21 @@ +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require("expo/metro-config"); +const path = require("path"); + +/** @type {import('expo/metro-config').MetroConfig} */ +const config = getDefaultConfig(__dirname); + +const { transformer, resolver } = config; + +config.transformer = { + ...transformer, + babelTransformerPath: require.resolve("react-native-svg-transformer"), +}; + +config.resolver = { + ...resolver, + assetExts: [...resolver.assetExts.filter((ext) => ext !== "svg"), "wasm"], + sourceExts: [...resolver.sourceExts, "svg", "wasm"], +}; + +module.exports = config; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8bebbc1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14482 @@ +{ + "name": "rcs-batsirai", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rcs-batsirai", + "version": "1.0.0", + "hasInstallScript": true, + "dependencies": { + "@bildau/rn-pdf-reader": "^4.2.7", + "@expo-google-fonts/inter": "^0.4.2", + "@expo-google-fonts/manrope": "^0.4.2", + "@expo-google-fonts/newsreader": "^0.4.1", + "@expo-google-fonts/plus-jakarta-sans": "^0.4.2", + "@expo-google-fonts/space-grotesk": "^0.4.1", + "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/netinfo": "11.4.1", + "@react-navigation/bottom-tabs": "^7.4.0", + "@react-navigation/elements": "^2.6.3", + "@react-navigation/native": "^7.1.8", + "@supabase/supabase-js": "^2.103.1", + "base-64": "^1.0.0", + "deprecated-react-native-prop-types": "^5.0.0", + "expo": "~54.0.34", + "expo-asset": "~12.0.13", + "expo-blur": "~15.0.8", + "expo-constants": "~18.0.13", + "expo-document-picker": "~14.0.8", + "expo-file-system": "~19.0.21", + "expo-font": "~14.0.11", + "expo-haptics": "~15.0.8", + "expo-image": "~3.0.11", + "expo-linking": "~8.0.12", + "expo-modules-core": "~3.0.30", + "expo-router": "~6.0.23", + "expo-splash-screen": "~31.0.13", + "expo-sqlite": "~16.0.10", + "expo-status-bar": "~3.0.9", + "expo-symbols": "~1.0.8", + "expo-system-ui": "~6.0.9", + "expo-web-browser": "~15.0.11", + "lucide-react": "^1.16.0", + "lucide-react-native": "^1.16.0", + "or": "^0.2.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "react-native-blob-util": "^0.24.7", + "react-native-drax": "^1.1.0", + "react-native-gesture-handler": "~2.28.0", + "react-native-pdf": "^7.0.4", + "react-native-pdf-renderer": "^2.3.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", + "react-native-web": "~0.21.0", + "react-native-webview": "13.15.0", + "react-native-worklets": "0.5.1" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "babel-preset-expo": "^55.0.22", + "eslint": "^9.25.0", + "eslint-config-expo": "~10.0.0", + "patch-package": "^8.0.1", + "postinstall-postinstall": "^2.1.0", + "react-native-svg-transformer": "^1.5.3", + "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/@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.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "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.29.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", + "integrity": "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==", + "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.29.0", + "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.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "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-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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/@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.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "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.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "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.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "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/@bildau/rn-pdf-reader": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@bildau/rn-pdf-reader/-/rn-pdf-reader-4.2.7.tgz", + "integrity": "sha512-hzdDHy6NmiIXAhotWd22BdnvLIFQpuX0M3idaNAuzLc11itHPK8uRWR58I4OWmx/RI9W7GyNesMZX3tSFUiaRA==", + "license": "MIT", + "dependencies": { + "buffer": "5.1.0", + "csstype": "2.6.8", + "js-base64": "2.4.5" + }, + "peerDependencies": { + "expo": ">= 42.0.x", + "expo-constants": ">= 9.x", + "expo-file-system": ">= 9.x", + "react": ">= 16.x", + "react-native": "*", + "react-native-webview": ">= 11.x" + } + }, + "node_modules/@bildau/rn-pdf-reader/node_modules/buffer": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", + "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/@bildau/rn-pdf-reader/node_modules/csstype": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", + "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==", + "license": "MIT" + }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@expo-google-fonts/inter": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/inter/-/inter-0.4.2.tgz", + "integrity": "sha512-syfiImMaDmq7cFi0of+waE2M4uSCyd16zgyWxdPOY7fN2VBmSLKEzkfbZgeOjJq61kSqPBNNtXjggiQiSD6gMQ==", + "license": "MIT AND OFL-1.1" + }, + "node_modules/@expo-google-fonts/manrope": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/manrope/-/manrope-0.4.2.tgz", + "integrity": "sha512-BZsKe8d9BJrVnIQIZcTS7/Kac0TbXnqs/+8EBSiQxrmK6GoCO6eTkmr50D1weIk/EoF20pTmAkpWICnovATr/g==", + "license": "MIT AND OFL-1.1" + }, + "node_modules/@expo-google-fonts/newsreader": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/newsreader/-/newsreader-0.4.1.tgz", + "integrity": "sha512-j96W112+mj/VYP06DCq00w+twyw42w0MY7eHB/XXioNK80BcG0cIk/zogjOENZSY7qejzIFbHE/PZYqlh/6VKQ==", + "license": "MIT AND OFL-1.1" + }, + "node_modules/@expo-google-fonts/plus-jakarta-sans": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/plus-jakarta-sans/-/plus-jakarta-sans-0.4.2.tgz", + "integrity": "sha512-6LYVmVGwjQvH+uzzWlVc9+oMj4lkNQ41aymVDjO+x8aFk8kCye20wOyLomYMZaMezA++Uf1mZRCw3W3Fy/hxEA==", + "license": "MIT AND OFL-1.1" + }, + "node_modules/@expo-google-fonts/space-grotesk": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/space-grotesk/-/space-grotesk-0.4.1.tgz", + "integrity": "sha512-ZVQYw4Ok/pgcSJiufP8oRZE3AVxS9xtmKEUfsurbHkHNdMc/GA1gDXP9G4Cr7KL4KqSc0haexR2TuMigotCn4Q==", + "license": "MIT AND OFL-1.1" + }, + "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.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "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.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "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.11", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.0.11.tgz", + "integrity": "sha512-xV+ps6YCW7XIPVUwFVCRN2nox09dnRwy8uIjwHWTODu0zFw4kp4omnVkl0OOjuu2XOe7tdgAHxikrkJt9xB/7Q==", + "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.5", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.15.5.tgz", + "integrity": "sha512-mdVoAMcux1WlM6kd1RoWiHRNqKqS+J6mKmWQ/BKgeh937S/fcW58EE68O6nc4KDXtWi3PBeNHskOFcgyIuD4hw==", + "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": "^10.2.2", + "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/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@expo/fingerprint/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@expo/fingerprint/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/fingerprint/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/image-utils": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.14.tgz", + "integrity": "sha512-5Sn+jG4Cw+shC2wDMXoqSAJnvERbiwzHn05FpWtD5IBflfTIs5gUmjzwiGVyjOdlMSQhgRrw/AymPbmO9h9mpQ==", + "license": "MIT", + "dependencies": { + "@expo/require-utils": "^55.0.5", + "@expo/spawn-async": "^1.7.2", + "chalk": "^4.0.0", + "getenv": "^2.0.0", + "jimp-compact": "0.16.1", + "parse-png": "^2.1.0", + "semver": "^7.6.0" + } + }, + "node_modules/@expo/image-utils/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/json-file": { + "version": "10.0.14", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.14.tgz", + "integrity": "sha512-yWwBFywFv+SxkJp/pIzzA416JVYflNUh7pqQzgaA6nXDqRyK7KfrqVzk8PdUfDnqbBcaZZxpzNssfQZzp5KHrA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "json5": "^2.2.3" + } + }, + "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.15", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-54.0.15.tgz", + "integrity": "sha512-SqIya4VZ9KHM1S9g+xR0A+QKw1Tfs7Gacx6bQNJ98vs4+O7I5+QP5mHZIB0QSZLUV8opiXebHYTiTu+0OAsIUw==", + "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", + "picomatch": "^4.0.3", + "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.4.3", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.4.3.tgz", + "integrity": "sha512-wbuj3EebM7W9hN/Wp4xTzKd6rQ2zKJzAxkFxkOOwyysLp0HOAgQ4/5RINyoS241pZUX2rUHq7mAJ7pcCQ8U0Ow==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/package-manager": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.10.5.tgz", + "integrity": "sha512-nCP9Mebfl3jvOr0/P6VAuyah6PAtun+aihIL2zAtuE8uSe94JWkVZ7051i0MUVO+y3gFpBqnr8IIH5ch+VJjHA==", + "license": "MIT", + "dependencies": { + "@expo/json-file": "^10.0.14", + "@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/@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/@expo/prebuild-config/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/require-utils": { + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.5.tgz", + "integrity": "sha512-U4K/CQ2VpXuwfNGsN+daKmYOt15hCP8v/pXaYH6eut7kdYZo6SfJ1yr67BIcJ+1Gzzs+QzTxswAZChKpXmceyw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8" + }, + "peerDependencies": { + "typescript": "^5.0.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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.8.0", + "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.8.0.tgz", + "integrity": "sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz", + "integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==", + "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.4", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.4.tgz", + "integrity": "sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==", + "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/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/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/@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/load-nyc-config/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/@istanbuljs/load-nyc-config/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/@istanbuljs/load-nyc-config/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/@istanbuljs/load-nyc-config/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/@istanbuljs/load-nyc-config/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/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "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/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "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-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, + "node_modules/@react-native-community/netinfo": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.4.1.tgz", + "integrity": "sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.59" + } + }, + "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.83.6", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.6.tgz", + "integrity": "sha512-qfRXsHGeucT5c6mK+8Q7v4Ly3zmygfVmFlEtkiq7q07W1OTreld6nib4rJ/DBEeNiKBoBTuHjWliYGNuDjLFQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.83.6" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-plugin-codegen/node_modules/@react-native/codegen": { + "version": "0.83.6", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.6.tgz", + "integrity": "sha512-doB/Pq6Cf6IjF3wlQXTIiZOnsX9X8mEEk+CdGfyuCwZjWrf7IB8KaZEXXckJmfUcIwvJ9u/a72ZoTTCIoxAc9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/babel-plugin-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", + "dev": true, + "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/babel-plugin-codegen/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/babel-plugin-codegen/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.83.6", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.6.tgz", + "integrity": "sha512-4/fXFDUvGOObETZq4+SUFkafld6OGgQWut5cQiqVghlhCB5z/p2lVhPgEUr/aTxTzeS3AmN+ztC+GpYPQ7tsTw==", + "dev": true, + "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.83.6", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "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/babel-preset/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", + "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-parser": "0.32.0" + } + }, + "node_modules/@react-native/babel-preset/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/babel-preset/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "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/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/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.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "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-color": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", + "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==", + "license": "MIT" + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "license": "MIT" + }, + "node_modules/@react-navigation/bottom-tabs": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.16.1.tgz", + "integrity": "sha512-wjFATJmbq0K8B96Ax0JcK2+Eu7syfYvQ5qUd/tgcv8JuCYLwKKqojJMAl31qdjpKqFG09pQ6TSdEDHOek60CAA==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.18", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^7.2.4", + "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.17.4", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.4.tgz", + "integrity": "sha512-Rv9E2oNNQEkPGpmu9q+vJwGJRSQR6LBg5L+Yo1QHjtwGbHUbjkIKOdYymDZoZYgNzX2OD4rAIlfuzbDKa3cCeA==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.5.5", + "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.18", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.18.tgz", + "integrity": "sha512-mKEvDr6CkCVYZSb8W9WubNseihL+1c8M7ktZJCTCbMk8rQgdQfkdRNwpSUQKspdGpUHCb9cyzvaiuzl1NtjVgw==", + "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.2.4", + "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.2.4", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.4.tgz", + "integrity": "sha512-eWC2D3JjhYLId2fVTZhhCiUpWIaPhO9XyEb7Wq8ElmOHyIODlbOzgZ0rKia02OIsDKr9BzZl2sK1dL70yMxDaw==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.17.4", + "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.15.1", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.15.1.tgz", + "integrity": "sha512-kNrJggwoB/onC0MpZIuZ6qaqeAziFchz+W9txBzhd6qbWmB1OkPVUnu6fWgc6BQc7MeMf59djVmqgX+6kJU1Ug==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.18", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.2.4", + "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.5", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.5.tgz", + "integrity": "sha512-9/hhMte12Kgu+pMnLfA4EWJ0OQmIEAMVMX06FPH2yGkEQSQ3JhhCN/GkcRikzQhtEi97VYYQA15umptBUShcOQ==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "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/@supabase/auth-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.1.tgz", + "integrity": "sha512-7eyheXfAGwkB9bZewJPs+N3UYt6kra2JG6mIxNEgbkvcO15PLD1e75PTIUEYYl3zrifm3GrpShVl7QZxKrXO/w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.1.tgz", + "integrity": "sha512-XbOPnR2mW7jp/EcW447xmGwCa+/Wc00Hkw8t4tUIJjRsHQ4xAESsLKcyLRhRJjJoUnJVXUlC+w0wUxUCM7CG2A==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.1.tgz", + "integrity": "sha512-Qbn6d2lqiqeaBX1Uko0e/hL90dtQGRN6CG2wMVQtJpRFstlVW45qmUTyTOsiB8dYUWu1fWYo4YzJuDbokGv3tQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.1.tgz", + "integrity": "sha512-eQCYri5E8KsjpDgC7g28cOOS2britjUWdNSJluFMainqrMRepzjOnaxqXc3RoAz7H0dxmBrfLUNF6NGP8C+YaA==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.1.tgz", + "integrity": "sha512-HWcLIhqinhWKpOQ3WzglR2unjW0eh9J7yOu3IZrZNIEkraK4La/HDvTqndljGsNw0itPtyHhuKBxRoPG1VUARw==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.1.tgz", + "integrity": "sha512-gP4HurGkGu7Z3xoOCjtAI17BKKp7jpsmwY0Ssbsks9XQRzJ7ZhK7LxfLdBSYgUdgZCQgjRK+Mr7+cl4Gxrk0Rw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.106.1", + "@supabase/functions-js": "2.106.1", + "@supabase/postgrest-js": "2.106.1", + "@supabase/realtime-js": "2.106.1", + "@supabase/storage-js": "2.106.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.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/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "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/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, + "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/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "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==", + "dev": 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/@typescript-eslint/eslint-plugin": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", + "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/type-utils": "8.59.4", + "@typescript-eslint/utils": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.4", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz", + "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz", + "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.4", + "@typescript-eslint/types": "^8.59.4", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz", + "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz", + "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz", + "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/utils": "8.59.4", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", + "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz", + "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.4", + "@typescript-eslint/tsconfig-utils": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz", + "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz", + "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.4", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "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.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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": "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/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/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "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.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "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": "55.0.22", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.22.tgz", + "integrity": "sha512-Se6kPnvCNN13jJVIa6JJvlmImVoVRzu9stagAbivCPcfrq2VNrsEiYpJZ1+H32kXinKW/y797/wctGuxPy0APw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.20.5", + "@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.83.6", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "expo-widgets": "^55.0.19", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + }, + "expo-widgets": { + "optional": true + } + } + }, + "node_modules/babel-preset-expo/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.1.tgz", + "integrity": "sha512-HgErPZTghW76Rkq9uqn5ESeiD97FbqpZ1V170T1RG2RDp+7pJVQV2pQJs7y5YzN0/gcT6GM5ci9apRnIwuyPdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-parser": "0.32.1" + } + }, + "node_modules/babel-preset-expo/node_modules/hermes-estree": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", + "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-preset-expo/node_modules/hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", + "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.1" + } + }, + "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/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "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.10.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", + "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/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/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "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": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "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/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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/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/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/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "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/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/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/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "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-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", + "license": "MIT", + "dependencies": { + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "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/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.360", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz", + "integrity": "sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==", + "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/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-expo": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-10.0.0.tgz", + "integrity": "sha512-/XC/DvniUWTzU7Ypb/cLDhDD4DXqEio4lug1ObD/oQ9Hcx3OVOR8Mkp4u6U4iGoZSJyIQmIk3WVHe/P1NYUXKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-expo": "^1.0.0", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^5.1.0", + "globals": "^16.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" + } + }, + "node_modules/eslint-config-expo/node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-expo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-1.0.3.tgz", + "integrity": "sha512-C1v9NPvpDET36+7Klpp/+53Jl+VzOfpbDxpKtL/pAPhCDwTX0kW6Swo425PT0uc4AMT5jpQbB7hSKFjKOGMl4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.29.1", + "@typescript-eslint/utils": "^8.29.1", + "eslint": "^9.24.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/expo": { + "version": "54.0.34", + "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.34.tgz", + "integrity": "sha512-XkVHguZZDC8BcTQxHAd14/TQFbDp1Wt0Z/KApO9t68Ll5A127hLCPzU+a9gytfCIiyL/V1IpF1vIcOLKEVAoNQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@expo/cli": "54.0.24", + "@expo/config": "~12.0.13", + "@expo/config-plugins": "~54.0.4", + "@expo/devtools": "0.1.8", + "@expo/fingerprint": "0.15.5", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "54.0.15", + "@expo/vector-icons": "^15.0.3", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~54.0.10", + "expo-asset": "~12.0.13", + "expo-constants": "~18.0.13", + "expo-file-system": "~19.0.22", + "expo-font": "~14.0.11", + "expo-keep-awake": "~15.0.8", + "expo-modules-autolinking": "3.0.25", + "expo-modules-core": "3.0.30", + "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.13", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.13.tgz", + "integrity": "sha512-x/p7WvQUnkn6K43b9eL6SPeq5Vnf1E8BDe9bDrWrvMqzyUvJnUFvl+ctg3034s/+UHe7Ne2pAmc0+yzbl8CrDQ==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.8.8", + "expo-constants": "~18.0.13" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-blur": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-15.0.8.tgz", + "integrity": "sha512-rWyE1NBRZEu9WD+X+5l7gyPRszw7n12cW3IRNAb5i6KFzaBp8cxqT5oeaphJapqURvcqhkOZn2k5EtBSbsuU7w==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "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-document-picker": { + "version": "14.0.8", + "resolved": "https://registry.npmjs.org/expo-document-picker/-/expo-document-picker-14.0.8.tgz", + "integrity": "sha512-3tyQKpPqWWFlI8p9RiMX1+T1Zge5mEKeBuXWp1h8PEItFMUDSiOJbQ112sfdC6Hxt8wSxreV9bCRl/NgBdt+fA==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-file-system": { + "version": "19.0.22", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.22.tgz", + "integrity": "sha512-l9pgahSc7sJD0bP9vBNeXvZjy8QKDpVHVxWmei/ESQOrzmoj5BidziqLVsyZdxsi+PfdbTtttLTAmddH/JafYA==", + "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-haptics": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-15.0.8.tgz", + "integrity": "sha512-lftutojy8Qs8zaDzzjwM3gKHFZ8bOOEZDCkmh2Ddpe95Ra6kt2izeOfOfKuP/QEh0MZ1j9TfqippyHdRd1ZM9g==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-3.0.11.tgz", + "integrity": "sha512-4TudfUCLgYgENv+f48omnU8tjS2S0Pd9EaON5/s1ZUBRwZ7K8acEr4NfvLPSaeXvxW24iLAiyQ7sV7BXQH3RoA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "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.12", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.12.tgz", + "integrity": "sha512-FpXeIpFgZuxihwT9lBo86YD3y6LphBuAhN680MMxm/Y7fmsc57vimn2d3vFu68VI0+Z9w457t494mu2wvlgWTQ==", + "license": "MIT", + "dependencies": { + "expo-constants": "~18.0.13", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-modules-autolinking": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.25.tgz", + "integrity": "sha512-YmHWctJlwvOuLZccg3cOXvSiXVJrPMKl7g2YR0YHWoGL9v2RvcmgaPJWPSLVW+voNEgEPsbo5UmUrAqbnYcBeg==", + "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.30", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-3.0.30.tgz", + "integrity": "sha512-a6IrpAn/Jbmwxi9L+hMmXKpNqnkUpoF7WHOpn02rVLyax2J0gB1vvCVE5rNydplEnt41Q6WxQwvcOjZaIkcSUg==", + "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-server": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.6.tgz", + "integrity": "sha512-vb5TBtskvEdzYuW79lATXutOEBfW5m6U4EFpNjCVZTnI7S//SAsLQkYEpn+EDfn84m6VQfzSGkIVR6YPaScKFA==", + "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-symbols": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-1.0.8.tgz", + "integrity": "sha512-7bNjK350PaQgxBf0owpmSYkdZIpdYYmaPttDBb2WIp6rIKtcEtdzdfmhsc2fTmjBURHYkg36+eCxBFXO25/1hw==", + "license": "MIT", + "dependencies": { + "sf-symbols-typescript": "^2.0.0" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-system-ui": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-6.0.9.tgz", + "integrity": "sha512-eQTYGzw1V4RYiYHL9xDLYID3Wsec2aZS+ypEssmF64D38aDrqbDgz1a2MSlHLQp2jHXSs3FvojhZ9FVela1Zcg==", + "license": "MIT", + "dependencies": { + "@react-native/normalize-colors": "0.81.5", + "debug": "^4.3.2" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "node_modules/expo-system-ui/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/expo-web-browser": { + "version": "15.0.11", + "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.11.tgz", + "integrity": "sha512-r2LS4Ro6DgUPZkcaEfgt8mp9eJuoA93x11Jh7S6utFe0FEzvUNn2yFhxg8XVwESaaHGt2k5V8LuK36rsp0BeIw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/@expo/cli": { + "version": "54.0.24", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.24.tgz", + "integrity": "sha512-5xse1bEgnVUBhOrtttc6xTNJVvjyTRavpzuF0/0nuj+312vfSbk7EiRbG+xJ2pW/iZxnhLPJkFCrPYG0nmheAQ==", + "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.15", + "@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.6", + "freeport-async": "^2.0.0", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "minimatch": "^9.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^4.0.3", + "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/@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/expo/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/expo/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/expo/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expo/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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/expo/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/expo/node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "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/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "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/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/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "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/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "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/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "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/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "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/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "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/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "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/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/js-base64": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", + "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==", + "license": "BSD-3-Clause" + }, + "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": "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/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/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "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/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "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.2.1", + "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.1.tgz", + "integrity": "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==", + "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/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "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.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "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": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "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/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "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/lucide-react": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lucide-react-native": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-1.16.0.tgz", + "integrity": "sha512-clxA22OvrNUHB2tQQEvURN8KJ5/vbUV/6GSctU1aCoFlJmDwNrdo8xxNTG535tu7iOATZCth6R2AW1kbva+g6A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-native": "*", + "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.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-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "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/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.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "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/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "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.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "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.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "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/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/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/or": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/or/-/or-0.2.0.tgz", + "integrity": "sha512-BHB8VZq2isxkyRaCBZ6CZCbQBzCT+gy8LPiqdbMH1+Fd6biFj3v8ebjeYzzL51PbsApsPYnGegGTO6KLQMxxDw==", + "engines": { + "node": "*" + } + }, + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "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/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true, + "license": "MIT" + }, + "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.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/plist/node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "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/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/postinstall-postinstall": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", + "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "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/rc/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/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.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "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-blob-util": { + "version": "0.24.9", + "resolved": "https://registry.npmjs.org/react-native-blob-util/-/react-native-blob-util-0.24.9.tgz", + "integrity": "sha512-tG3+m0WhVdBGifvxSFxZDVqtr85D0fGBJU6E4UxmK3tU+RabJZTumXEn8k7jn5/NFe8OhQhPjtBEZ11ZJ6L7Vw==", + "license": "MIT", + "dependencies": { + "base-64": "0.1.0", + "glob": "13.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ronradtke" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-blob-util/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/react-native-blob-util/node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, + "node_modules/react-native-blob-util/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/react-native-blob-util/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/react-native-blob-util/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/react-native-drax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-native-drax/-/react-native-drax-1.1.0.tgz", + "integrity": "sha512-MBAmlMZe/sSCwq2EXdxH8Dc2Gy9y+tvRJn/EWPtAsEoRkIVWeCFg8+7q7ABkmIDLwHGobuIRVC5M6OL6+2ldgQ==", + "license": "MIT", + "workspaces": [ + "example", + "docs-site" + ], + "peerDependencies": { + "react": ">=18.0.0", + "react-native": ">=0.68.0", + "react-native-gesture-handler": ">=2.0.0", + "react-native-reanimated": "^4.0.0" + } + }, + "node_modules/react-native-gesture-handler": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", + "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-is-edge-to-edge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", + "integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-pdf": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-7.0.4.tgz", + "integrity": "sha512-Uhxn5SSguMKvbwD50iIZfYPmYeKcX+9I3tc4J3HCbRExAN0uqXTAKx2pvbR2Y62umAj0JS274FcbSldQfoQfyg==", + "license": "MIT", + "dependencies": { + "crypto-js": "4.2.0", + "deprecated-react-native-prop-types": "^2.3.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-blob-util": ">=0.13.7" + } + }, + "node_modules/react-native-pdf-renderer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-native-pdf-renderer/-/react-native-pdf-renderer-2.3.0.tgz", + "integrity": "sha512-k/eacZVUKAcTTVvZk5Ey+wiJYRc9K2OdZ/10CJWH7xlxyzfAg6LHjjm8NIOQTJgnL9tXSDjNihjdVldWb+TxJA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": ">=0.71" + } + }, + "node_modules/react-native-pdf/node_modules/deprecated-react-native-prop-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz", + "integrity": "sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==", + "license": "MIT", + "dependencies": { + "@react-native/normalize-color": "*", + "invariant": "*", + "prop-types": "*" + } + }, + "node_modules/react-native-reanimated": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.7.tgz", + "integrity": "sha512-Q4H6xA3Tn7QL0/E/KjI86I1KK4tcf+ErRE04LH34Etka2oVQhW6oXQ+Q8ZcDCVxiWp5vgbBH6XcH8BOo4w/Rhg==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "^1.2.1", + "semver": "^7.7.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "0.78 - 0.82", + "react-native-worklets": "0.5 - 0.8" + } + }, + "node_modules/react-native-reanimated/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "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-svg": { + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", + "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-svg-transformer": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-1.5.3.tgz", + "integrity": "sha512-M4uFg5pUt35OMgjD4rWWbwd6PmxV96W7r/gQTTa+iZA5B+jO6aURhzAZGLHSrg1Kb91cKG0Rildy9q1WJvYstg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0", + "@svgr/plugin-svgo": "^8.1.0", + "path-dirname": "^1.0.2" + }, + "peerDependencies": { + "react-native": ">=0.59.0", + "react-native-svg": ">=12.0.0" + } + }, + "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-webview": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", + "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "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/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/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-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/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/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "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-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/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "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": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.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-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/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/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "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/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "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-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/simple-swizzle/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/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.9", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", + "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "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/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-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/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/svgo": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/svgo/node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/tar": { + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "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/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/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.47.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz", + "integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==", + "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/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/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.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "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/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "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/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.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/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==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "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/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wonka": { + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.6.tgz", + "integrity": "sha512-MXH+6mDHAZ2GuMpgKS055FR6v0xVP3XwquxIMYXgiW+FejHQlMGlvVRZT4qMCxR+bEo/FCtIdKxwej9WV3YQag==", + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "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..9b14edf --- /dev/null +++ b/package.json @@ -0,0 +1,78 @@ +{ + "name": "rcs-batsirai", + "main": "expo-router/entry", + "version": "1.0.0", + "scripts": { + "start": "expo start", + "reset-project": "node ./scripts/reset-project.js", + "android": "expo run:android", + "ios": "expo run:ios", + "web": "expo start --web", + "lint": "expo lint", + "postinstall": "patch-package" + }, + "dependencies": { + "@bildau/rn-pdf-reader": "^4.2.7", + "@expo-google-fonts/inter": "^0.4.2", + "@expo-google-fonts/manrope": "^0.4.2", + "@expo-google-fonts/newsreader": "^0.4.1", + "@expo-google-fonts/plus-jakarta-sans": "^0.4.2", + "@expo-google-fonts/space-grotesk": "^0.4.1", + "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/netinfo": "11.4.1", + "@react-navigation/bottom-tabs": "^7.4.0", + "@react-navigation/elements": "^2.6.3", + "@react-navigation/native": "^7.1.8", + "@supabase/supabase-js": "^2.103.1", + "base-64": "^1.0.0", + "deprecated-react-native-prop-types": "^5.0.0", + "expo": "~54.0.34", + "expo-asset": "~12.0.13", + "expo-blur": "~15.0.8", + "expo-constants": "~18.0.13", + "expo-document-picker": "~14.0.8", + "expo-file-system": "~19.0.21", + "expo-font": "~14.0.11", + "expo-haptics": "~15.0.8", + "expo-image": "~3.0.11", + "expo-linking": "~8.0.12", + "expo-modules-core": "~3.0.30", + "expo-router": "~6.0.23", + "expo-splash-screen": "~31.0.13", + "expo-sqlite": "~16.0.10", + "expo-status-bar": "~3.0.9", + "expo-symbols": "~1.0.8", + "expo-system-ui": "~6.0.9", + "expo-web-browser": "~15.0.11", + "lucide-react": "^1.16.0", + "lucide-react-native": "^1.16.0", + "or": "^0.2.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "react-native-blob-util": "^0.24.7", + "react-native-drax": "^1.1.0", + "react-native-gesture-handler": "~2.28.0", + "react-native-pdf": "^7.0.4", + "react-native-pdf-renderer": "^2.3.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", + "react-native-web": "~0.21.0", + "react-native-webview": "13.15.0", + "react-native-worklets": "0.5.1" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "babel-preset-expo": "^55.0.22", + "eslint": "^9.25.0", + "eslint-config-expo": "~10.0.0", + "patch-package": "^8.0.1", + "postinstall-postinstall": "^2.1.0", + "react-native-svg-transformer": "^1.5.3", + "typescript": "~5.9.2" + }, + "private": true +} diff --git a/package.json.2D_q-2-Nu-kn7eViVb2lHdu0L6LI5-jogE7lgzsx8pU b/package.json.2D_q-2-Nu-kn7eViVb2lHdu0L6LI5-jogE7lgzsx8pU new file mode 100644 index 0000000..21293e0 --- /dev/null +++ b/package.json.2D_q-2-Nu-kn7eViVb2lHdu0L6LI5-jogE7lgzsx8pU @@ -0,0 +1,58 @@ +{ + "name": "rcs-batsirai", + "main": "expo-router/entry", + "version": "1.0.0", + "scripts": { + "start": "expo start", + "reset-project": "node ./scripts/reset-project.js", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "lint": "expo lint" + }, + "dependencies": { + "@expo-google-fonts/inter": "^0.4.2", + "@expo-google-fonts/manrope": "^0.4.2", + "@expo-google-fonts/newsreader": "^0.4.1", + "@expo-google-fonts/plus-jakarta-sans": "^0.4.2", + "@expo-google-fonts/space-grotesk": "^0.4.1", + "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "^3.0.2", + "@react-native-community/netinfo": "^12.0.1", + "@react-navigation/bottom-tabs": "^7.4.0", + "@react-navigation/elements": "^2.6.3", + "@react-navigation/native": "^7.1.8", + "@supabase/supabase-js": "^2.103.1", + "expo": "~54.0.33", + "expo-constants": "~18.0.13", + "expo-font": "~14.0.11", + "expo-haptics": "~15.0.8", + "expo-image": "~3.0.11", + "expo-linking": "~8.0.11", + "expo-router": "~6.0.23", + "expo-splash-screen": "~31.0.13", + "expo-sqlite": "^55.0.15", + "expo-status-bar": "~3.0.9", + "expo-symbols": "~1.0.8", + "expo-system-ui": "~6.0.9", + "expo-web-browser": "~15.0.10", + "lucide-react-native": "^0.473.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "react-native-gesture-handler": "~2.28.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", + "react-native-web": "~0.21.0", + "react-native-worklets": "0.5.1" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "eslint": "^9.25.0", + "eslint-config-expo": "~10.0.0", + "typescript": "~5.9.2" + }, + "private": true +} diff --git a/patches/@bildau+rn-pdf-reader+4.2.7.patch b/patches/@bildau+rn-pdf-reader+4.2.7.patch new file mode 100644 index 0000000..d24d314 --- /dev/null +++ b/patches/@bildau+rn-pdf-reader+4.2.7.patch @@ -0,0 +1,186 @@ +diff --git a/node_modules/@bildau/rn-pdf-reader/lib/index.js b/node_modules/@bildau/rn-pdf-reader/lib/index.js +index 2483bd2..c2c49a5 100644 +--- a/node_modules/@bildau/rn-pdf-reader/lib/index.js ++++ b/node_modules/@bildau/rn-pdf-reader/lib/index.js +@@ -1,7 +1,7 @@ + import * as React from 'react'; + import { View, ActivityIndicator, Platform, StyleSheet } from 'react-native'; + import { WebView } from 'react-native-webview'; +-import * as FileSystem from 'expo-file-system'; ++import * as FileSystem from 'expo-file-system/legacy'; + const { cacheDirectory, writeAsStringAsync, deleteAsync, getInfoAsync, EncodingType, } = FileSystem; + function viewerHtml(base64, customStyle, withScroll = false, withPinchZoom = false, maximumPinchZoomScale = 5) { + return ` +@@ -48,10 +48,14 @@ const bundleJsPath = `${cacheDirectory}bundle.js`; + const htmlPath = `${cacheDirectory}index.html`; + const pdfPath = `${cacheDirectory}file.pdf`; + function writeWebViewComponentFile(container, fileName, callback) { ++ console.log(`[PdfReader] writing component file: ${fileName}`); + writeAsStringAsync(`${cacheDirectory}${fileName}`, container.getBundle()).then(() => { ++ console.log(`[PdfReader] finished writing: ${fileName}`); + if (typeof callback === 'function') { + callback(); + } ++ }).catch(err => { ++ console.error(`[PdfReader] error writing ${fileName}:`, err); + }); + } + function writeWebViewComponentFiles() { +@@ -66,15 +70,19 @@ function writeWebViewComponentFiles() { + }); + } + async function writeWebViewReaderFileAsync(data, customStyle, withScroll, withPinchZoom, maximumPinchZoomScale) { ++ console.log('[PdfReader] writeWebViewReaderFileAsync start'); + writeWebViewComponentFiles(); ++ console.log('[PdfReader] checking bundle.js exists...'); + const { exists, md5 } = await getInfoAsync(bundleJsPath, { md5: true }); + const bundleContainer = require('./bundleContainer'); + if (__DEV__ || !exists || bundleContainer.getBundleMd5() !== md5) { ++ console.log('[PdfReader] writing bundle.js...'); + await writeAsStringAsync(bundleJsPath, bundleContainer.getBundle()); + } ++ console.log('[PdfReader] writing index.html...'); + await writeAsStringAsync(htmlPath, viewerHtml(data, customStyle, withScroll, withPinchZoom, maximumPinchZoomScale)); +-} +-async function writePDFAsync(base64) { ++ console.log('[PdfReader] writeWebViewReaderFileAsync finished'); ++}async function writePDFAsync(base64) { + await writeAsStringAsync(pdfPath, base64.replace('data:application/pdf;base64,', ''), { encoding: EncodingType.Base64 }); + } + export async function removeFilesAsync() { +@@ -150,6 +158,7 @@ class PdfReader extends React.Component { + data: undefined, + renderedOnce: false, + }; ++ this.cachedWebviewSource = null; + this.validate = () => { + const { onError: propOnError, source } = this.props; + const { renderType } = this.state; +@@ -176,33 +185,40 @@ class PdfReader extends React.Component { + }; + this.init = async () => { + try { ++ console.log('[PdfReader] init start'); + const { source, customStyle, withScroll, withPinchZoom, maximumPinchZoomScale, } = this.props; + const { renderType } = this.state; ++ console.log('[PdfReader] renderType:', renderType); + switch (renderType) { + case 'GOOGLE_DRIVE_VIEWER': { + break; + } + case 'URL_TO_BASE64': { ++ console.log('[PdfReader] fetching PDF...'); + const data = await fetchPdfAsync(source); ++ console.log('[PdfReader] writing files...'); + await writeWebViewReaderFileAsync(data, customStyle, withScroll, withPinchZoom, maximumPinchZoomScale); + break; + } + case 'DIRECT_BASE64': { ++ console.log('[PdfReader] writing files (direct base64)...'); + await writeWebViewReaderFileAsync(source.base64, customStyle, withScroll, withPinchZoom, maximumPinchZoomScale); + break; + } + case 'BASE64_TO_LOCAL_PDF': { ++ console.log('[PdfReader] writing local PDF...'); + await writePDFAsync(source.base64); + break; + } + default: + break; + } ++ console.log('[PdfReader] init finished, setting ready: true'); + this.setState({ ready: true }); + } + catch (error) { + alert(`Sorry, an error occurred. ${error.message}`); +- console.error(error); ++ console.error('[PdfReader] init error:', error); + } + }; + this.getRenderType = () => { +@@ -232,23 +248,33 @@ class PdfReader extends React.Component { + this.getWebviewSource = () => { + const { renderType } = this.state; + const { source: { uri, headers }, onError, } = this.props; ++ let result; + switch (renderType) { + case 'GOOGLE_READER': +- return { uri: getGoogleReaderUrl(uri) }; ++ result = { uri: getGoogleReaderUrl(uri) }; ++ break; + case 'GOOGLE_DRIVE_VIEWER': +- return { uri: getGoogleDriveUrl(uri) }; ++ result = { uri: getGoogleDriveUrl(uri) }; ++ break; + case 'DIRECT_BASE64': + case 'URL_TO_BASE64': +- return { uri: htmlPath }; ++ result = { uri: htmlPath }; ++ break; + case 'DIRECT_URL': +- return { uri: uri, headers }; ++ result = { uri: uri, headers }; ++ break; + case 'BASE64_TO_LOCAL_PDF': +- return { uri: pdfPath }; ++ result = { uri: pdfPath }; ++ break; + default: { + onError('Unknown RenderType'); + return undefined; + } + } ++ if (!this.cachedWebviewSource || this.cachedWebviewSource.uri !== result.uri) { ++ this.cachedWebviewSource = result; ++ } ++ return this.cachedWebviewSource; + }; + } + componentDidMount() { +@@ -262,6 +288,7 @@ class PdfReader extends React.Component { + if (prevProps.source.uri !== this.props.source.uri || + prevProps.source.base64 !== this.props.source.base64) { + this.setState({ ready: false, renderType: this.getRenderType() }); ++ this.cachedWebviewSource = null; + this.validate(); + this.init(); + } +@@ -294,20 +321,33 @@ class PdfReader extends React.Component { + const isAndroid = Platform.OS === 'android'; + if (ready) { + const source = this.getWebviewSource(); ++ console.log('[PdfReader] rendering WebView with source:', source); + return (React.createElement(View, { style: [styles.container, containerStyle] }, + React.createElement(WebView, { ...{ + originWhitelist, + onLoad: (event) => { +- this.setState({ renderedOnce: true }); ++ console.log('[PdfReader] WebView onLoad called'); ++ if (!this.state.renderedOnce) { ++ this.setState({ renderedOnce: true }); ++ } + if (onLoad) { + onLoad(event); + } + }, +- onLoadEnd, +- onError, +- onHttpError: onError, ++ onLoadEnd: () => { ++ console.log('[PdfReader] WebView onLoadEnd called'); ++ if (onLoadEnd) onLoadEnd(); ++ }, ++ onError: (error) => { ++ console.error('[PdfReader] WebView onError:', error); ++ if (onError) onError(error); ++ }, ++ onHttpError: (error) => { ++ console.error('[PdfReader] WebView onHttpError:', error); ++ if (onError) onError(error); ++ }, + style, +- source: renderedOnce || !isAndroid ? source : undefined, ++ source: source, + }, allowFileAccess: isAndroid, allowFileAccessFromFileURLs: isAndroid, allowUniversalAccessFromFileURLs: isAndroid, scalesPageToFit: Platform.select({ android: false }), mixedContentMode: isAndroid ? 'always' : undefined, sharedCookiesEnabled: false, startInLoadingState: !noLoader, renderLoading: () => (noLoader ? React.createElement(View, null) : React.createElement(Loader, null)), ...webviewProps }))); + } + return !noLoader && !ready && React.createElement(Loader, null); diff --git a/patches/@supabase+supabase-js+2.106.1.patch b/patches/@supabase+supabase-js+2.106.1.patch new file mode 100644 index 0000000..ba12549 --- /dev/null +++ b/patches/@supabase+supabase-js+2.106.1.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@supabase/supabase-js/dist/index.cjs b/node_modules/@supabase/supabase-js/dist/index.cjs +index 56a9c3a..c57ea9d 100644 +--- a/node_modules/@supabase/supabase-js/dist/index.cjs ++++ b/node_modules/@supabase/supabase-js/dist/index.cjs +@@ -624,7 +624,7 @@ var require_extract = /* @__PURE__ */ __commonJSMin(((exports) => { + let otelModulePromise = null; + const OTEL_PKG = "@opentelemetry/api"; + function loadOtel() { +- if (otelModulePromise === null) otelModulePromise = Promise.resolve(`${OTEL_PKG}`).then((s) => tslib_1.__importStar(require(s))).catch(() => null); ++ otelModulePromise = Promise.resolve(null); + return otelModulePromise; + } + /** +diff --git a/node_modules/@supabase/supabase-js/dist/index.mjs b/node_modules/@supabase/supabase-js/dist/index.mjs +index 0b33345..fc81723 100644 +--- a/node_modules/@supabase/supabase-js/dist/index.mjs ++++ b/node_modules/@supabase/supabase-js/dist/index.mjs +@@ -68,7 +68,7 @@ function __awaiter(thisArg, _arguments, P, generator) { + let otelModulePromise = null; + const OTEL_PKG = "@opentelemetry/api"; + function loadOtel() { +- if (otelModulePromise === null) otelModulePromise = import(/* webpackIgnore: true */ /* turbopackIgnore: true */ /* @vite-ignore */ OTEL_PKG).catch(() => null); ++ if (otelModulePromise === null) otelModulePromise = Promise.resolve(null); + return otelModulePromise; + } + /** diff --git a/scripts/reset-project.js b/scripts/reset-project.js new file mode 100644 index 0000000..51dff15 --- /dev/null +++ b/scripts/reset-project.js @@ -0,0 +1,112 @@ +#!/usr/bin/env node + +/** + * This script is used to reset the project to a blank state. + * It deletes or moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example based on user input and creates a new /app directory with an index.tsx and _layout.tsx file. + * You can remove the `reset-project` script from package.json and safely delete this file after running it. + */ + +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); + +const root = process.cwd(); +const oldDirs = ["app", "components", "hooks", "constants", "scripts"]; +const exampleDir = "app-example"; +const newAppDir = "app"; +const exampleDirPath = path.join(root, exampleDir); + +const indexContent = `import { Text, View } from "react-native"; + +export default function Index() { + return ( + + Edit app/index.tsx to edit this screen. + + ); +} +`; + +const layoutContent = `import { Stack } from "expo-router"; + +export default function RootLayout() { + return ; +} +`; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const moveDirectories = async (userInput) => { + try { + if (userInput === "y") { + // Create the app-example directory + await fs.promises.mkdir(exampleDirPath, { recursive: true }); + console.log(`📁 /${exampleDir} directory created.`); + } + + // Move old directories to new app-example directory or delete them + for (const dir of oldDirs) { + const oldDirPath = path.join(root, dir); + if (fs.existsSync(oldDirPath)) { + if (userInput === "y") { + const newDirPath = path.join(root, exampleDir, dir); + await fs.promises.rename(oldDirPath, newDirPath); + console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`); + } else { + await fs.promises.rm(oldDirPath, { recursive: true, force: true }); + console.log(`❌ /${dir} deleted.`); + } + } else { + console.log(`➡️ /${dir} does not exist, skipping.`); + } + } + + // Create new /app directory + const newAppDirPath = path.join(root, newAppDir); + await fs.promises.mkdir(newAppDirPath, { recursive: true }); + console.log("\n📁 New /app directory created."); + + // Create index.tsx + const indexPath = path.join(newAppDirPath, "index.tsx"); + await fs.promises.writeFile(indexPath, indexContent); + console.log("📄 app/index.tsx created."); + + // Create _layout.tsx + const layoutPath = path.join(newAppDirPath, "_layout.tsx"); + await fs.promises.writeFile(layoutPath, layoutContent); + console.log("📄 app/_layout.tsx created."); + + console.log("\n✅ Project reset complete. Next steps:"); + console.log( + `1. Run \`npx expo start\` to start a development server.\n2. Edit app/index.tsx to edit the main screen.${ + userInput === "y" + ? `\n3. Delete the /${exampleDir} directory when you're done referencing it.` + : "" + }` + ); + } catch (error) { + console.error(`❌ Error during script execution: ${error.message}`); + } +}; + +rl.question( + "Do you want to move existing files to /app-example instead of deleting them? (Y/n): ", + (answer) => { + const userInput = answer.trim().toLowerCase() || "y"; + if (userInput === "y" || userInput === "n") { + moveDirectories(userInput).finally(() => rl.close()); + } else { + console.log("❌ Invalid input. Please enter 'Y' or 'N'."); + rl.close(); + } + } +); diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..ef8c48c --- /dev/null +++ b/src/app/(tabs)/_layout.tsx @@ -0,0 +1,71 @@ +import FloatingAIDock from "@/src/components/FloatingAIDock"; +import GlassBottomTabBar from "@/src/components/GlassBottomTabBar"; +import { FONTS } from "@/src/constants/Theme"; +import { TabBarVisibilityProvider } from "@/src/hooks/useAutoHideTabBar"; +import { useTheme } from "@/src/hooks/useTheme"; +import { Tabs } from "expo-router"; +import { Calendar, LayoutDashboard, Library } from "lucide-react-native"; +import React from "react"; + +export default function TabLayout() { + const { colors } = useTheme(); + + const screenOptions = { + tabBarActiveTintColor: colors.primary, + tabBarInactiveTintColor: colors.outline, + headerShown: false, + tabBarStyle: { + position: "absolute", + borderTopWidth: 0, + elevation: 0, + }, + tabBarLabelStyle: { + fontFamily: FONTS.label, + fontSize: 10, + letterSpacing: 0.5, + }, + } as any; + + return ( + + } + screenOptions={screenOptions} + > + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + + + + + {/* Extra screens still exist in the app, but are intentionally excluded from the bottom glass tab bar. + Keep these routes reachable via menu, buttons, or a future drawer/More screen. + */} + + ); +} diff --git a/src/app/(tabs)/aa_ai.tsx b/src/app/(tabs)/aa_ai.tsx new file mode 100644 index 0000000..e8d5f3a --- /dev/null +++ b/src/app/(tabs)/aa_ai.tsx @@ -0,0 +1,3 @@ +import AIScreen from '@/src/screens/AIScreen'; + +export default AIScreen; diff --git a/src/app/(tabs)/calendar.tsx b/src/app/(tabs)/calendar.tsx new file mode 100644 index 0000000..16e7e04 --- /dev/null +++ b/src/app/(tabs)/calendar.tsx @@ -0,0 +1,2982 @@ +import AutoHideScrollView from "@/src/components/AutoHideScrollView"; +import { CalendarStrip } from "@/src/components/ui/CalendarStrip"; +import { TimeInput } from "@/src/components/ui/TimeInput"; +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { getDb } from "@/src/db/database"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useData } from "@/src/hooks/useData"; +import { useTheme } from "@/src/hooks/useTheme"; +import { getLocalDateString } from "@/src/lib/date-utils"; +import { performMutation } from "@/src/lib/sync"; +import * as Haptics from "expo-haptics"; +import { useRouter } from "expo-router"; +import { + AlertCircle, + Calendar as CalendarIcon, + Check, + ChevronRight, + Clock, + History as HistoryIcon, + Layers, + MapPin, + Menu, + Plus, + RefreshCw, + Settings, + Sparkles, + Target, + Trash2, + X, + Zap +} from "lucide-react-native"; +import React, { + useEffect, + useMemo, + useRef, + useState +} from "react"; +import { + Alert, + Image, + KeyboardAvoidingView, + Modal, + Platform, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View +} from "react-native"; +import { DraxProvider, DraxView } from "react-native-drax"; +import Animated, { + useAnimatedStyle, + useSharedValue, + withSequence, + withSpring, +} from "react-native-reanimated"; +import { SafeAreaView } from "react-native-safe-area-context"; + +type Schedule = { + id: string; + date: string; + time_blocks: string; +}; + +type TimeBlock = { + start: string; + end: string; + task: string; + description?: string; + type?: "deep-work" | "sleep" | "walk" | "default" | "break" | "chores"; + todos?: { id: string; text: string; completed: boolean }[]; + task_id?: string; +}; + +const TIME_MARKERS = Array.from({ length: 24 }, (_, index) => { + const hour = index.toString().padStart(2, "0"); + return `${hour}:00`; +}); + +const parseTimeString = (time: string) => { + const [hours = "0", minutes = "0"] = time.split(":"); + return { hours: Number(hours), minutes: Number(minutes) }; +}; + +const getTimeOffset = (time: string, hourHeight: number) => { + const { hours, minutes } = parseTimeString(time); + return hours * hourHeight + (minutes * hourHeight) / 60; +}; + +const getBlockHeight = (start: string, end: string, hourHeight: number) => { + const startTime = parseTimeString(start); + const endTime = parseTimeString(end); + const startMinutes = startTime.hours * 60 + startTime.minutes; + const endMinutes = endTime.hours * 60 + endTime.minutes; + return Math.max( + hourHeight * 0.4, + ((endMinutes - startMinutes) * hourHeight) / 60, + ); +}; + +const toMinutes = (time: string) => { + const [hours = "0", minutes = "0"] = time.split(":"); + return Number(hours) * 60 + Number(minutes); +}; + +const toTimeString = (minutes: number) => { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`; +}; + +const splitDeepWorkBlock = ( + block: TimeBlock, + focusDuration: number, + breakDuration: number, +) => { + const blocks: TimeBlock[] = []; + const start = toMinutes(block.start); + const end = toMinutes(block.end); + const LUNCH_START = 12 * 60; + const LUNCH_END = 13 * 60; + const bridgesLunch = start < LUNCH_START && end > LUNCH_END; + let current = start; + + const pushFocusSegment = (segmentEnd: number) => { + blocks.push({ + ...block, + start: toTimeString(current), + end: toTimeString(segmentEnd), + originalStart: block.originalStart || block.start, + }); + current = segmentEnd; + }; + + const pushBreakSegment = (breakEnd: number) => { + blocks.push({ + task: "Break", + type: "break", + start: toTimeString(current), + end: toTimeString(breakEnd), + originalStart: block.originalStart || block.start, + }); + current = breakEnd; + }; + + while (current < end) { + if (bridgesLunch && current === LUNCH_START) { + blocks.push({ + task: "Lunch", + type: "default", + start: toTimeString(LUNCH_START), + end: toTimeString(LUNCH_END), + originalStart: block.originalStart || block.start, + }); + current = LUNCH_END; + continue; + } + + const segmentEnd = Math.min( + current + focusDuration, + end, + bridgesLunch && current < LUNCH_START ? LUNCH_START : end, + ); + pushFocusSegment(segmentEnd); + + if (current >= end) break; + if (bridgesLunch && current === LUNCH_START) continue; + + const nextBreakEnd = Math.min( + current + breakDuration, + end, + bridgesLunch && current < LUNCH_START ? LUNCH_START : end, + ); + if (nextBreakEnd > current) { + pushBreakSegment(nextBreakEnd); + } + } + + return blocks; +}; + +const normalizeTimeBlocks = (blocks: TimeBlock[], focusDuration: number) => { + const breakDuration = Math.min( + 15, + Math.max(5, Math.round(focusDuration * 0.25)), + ); + return blocks + .flatMap((block) => { + const duration = toMinutes(block.end) - toMinutes(block.start); + if (block.type === "deep-work" && duration > focusDuration) { + return splitDeepWorkBlock(block, focusDuration, breakDuration); + } + return [{ ...block, originalStart: block.start }]; + }) + .sort((a, b) => a.start.localeCompare(b.start)); +}; + +function processCluster(cluster: any[], result: any[]) { + const columns: any[][] = []; + cluster.forEach((block) => { + let placed = false; + for (let i = 0; i < columns.length; i++) { + const lastInCol = columns[i][columns[i].length - 1]; + if (block.start >= lastInCol.end) { + columns[i].push(block); + block.column = i; + placed = true; + break; + } + } + if (!placed) { + block.column = columns.length; + columns.push([block]); + } + }); + + cluster.forEach((block) => { + block.totalCols = columns.length; + result.push(block); + }); +} + +interface Habit { + id: string; + title: string; + preferred_time: string; + location: string; + is_done_today: number; +} + +export default function CalendarScreen() { + const { colors, sprintDuration } = useTheme(); + const { user } = useAuth(); + const router = useRouter(); + + const userId = user?.id || "guest"; + const today = getLocalDateString(); + const [selectedDate, setSelectedDate] = useState(today); + + const [showModal, setShowModal] = useState(false); + const [editingBlockIndex, setEditingBlockIndex] = useState( + null, + ); + + const [blockTitle, setBlockTitle] = useState(""); + const [blockStart, setBlockStart] = useState("09:00"); + const [blockEnd, setBlockEnd] = useState("10:00"); + const [blockType, setBlockType] = useState< + "event" | "deep-work" | "walk" | "break" + >("event"); + const [blockLocation, setBlockLocation] = useState(""); + const [blockDescription, setBlockDescription] = useState(""); + const [blockTodos, setBlockTodos] = useState< + { id: string; text: string; completed: boolean }[] + >([]); + const [newTodoText, setNewTodoText] = useState(""); + + const [hoverBlock, setHoverBlock] = useState(null); + const [dropCollision, setDropCollision] = useState(false); + const [activeActionBlock, setActiveActionBlock] = useState(null); + const [isMultiSelectMode, setIsMultiSelectMode] = useState(false); + const [selectedBlockKeys, setSelectedBlockKeys] = useState([]); + + // UNDO STATE + const [undoState, setUndoState] = useState<{ + blocks: any[]; + scheduleId: string; + } | null>(null); + const [showUndoToast, setShowUndoToast] = useState(false); + const undoTimeout = useRef(null); + + const triggerHaptic = ( + type: + | "light" + | "medium" + | "heavy" + | "selection" + | "success" + | "warning" + | "error", + ) => { + switch (type) { + case "light": + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + break; + case "medium": + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + break; + case "heavy": + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); + break; + case "selection": + Haptics.selectionAsync(); + break; + case "success": + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + break; + case "warning": + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); + break; + case "error": + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + break; + } + }; + + const toggleBlockSelection = (block: any) => { + const key = `${block.start}-${block.task}`; + setSelectedBlockKeys((prev) => + prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key], + ); + triggerHaptic("light"); + }; + + const getSelectedBlocks = () => { + return combinedBlocks.filter((b) => + selectedBlockKeys.includes(`${b.start}-${b.task}`), + ); + }; + + const ghostTop = useSharedValue(0); + const ghostScale = useSharedValue(1); + const lastSnap = useRef(-1); + const lastDragY = useRef(null); + const scrollRef = useRef(null); + const timelineRef = useRef(null); + const [timelineLayout, setTimelineLayout] = useState({ y: 0, height: 0 }); + + const measureTimeline = () => { + timelineRef.current?.measureInWindow((x, y, width, height) => { + console.log("TIMELINE MEASURED", { y, height }); + setTimelineLayout({ y, height }); + }); + }; + + const scrollY = useRef(0); + const scrollInterval = useRef(null); + + // SYNC TRACKING STATE + const [trackedSyncIds, setTrackedSyncIds] = useState< + Record< + number, + { + status: "pending" | "synced" | "failed"; + blocks: string[]; + timestamp: number; + } + > + >({}); + + useEffect(() => { + if (Object.keys(trackedSyncIds).length === 0) return; + + const checkSyncStatus = async () => { + const db = await getDb(); + const pendingIds: number[] = ( + await db.getAllAsync("SELECT id FROM sync_queue") + ).map((item) => item.id); + + setTrackedSyncIds((prev) => { + const next = { ...prev }; + let changed = false; + + Object.keys(next).forEach((idStr) => { + const id = parseInt(idStr); + if (next[id].status === "pending" && !pendingIds.includes(id)) { + next[id] = { ...next[id], status: "synced" }; + changed = true; + } + + // Clear old indicators after 30 seconds + if (Date.now() - next[id].timestamp > 30000) { + delete next[id]; + changed = true; + } + }); + + return changed ? next : prev; + }); + }; + + const interval = setInterval(checkSyncStatus, 2000); + return () => clearInterval(interval); + }, [trackedSyncIds]); + + const addTrackedSync = (syncId: number | undefined, blocks: any[]) => { + if (!syncId) return; + const blockKeys = blocks.map((b) => `${b.start}-${b.task}`); + setTrackedSyncIds((prev) => ({ + ...prev, + [syncId]: { status: "pending", blocks: blockKeys, timestamp: Date.now() }, + })); + }; + + const getSyncStatusForBlock = (block: any) => { + const key = `${block.start}-${block.task}`; + const entry = Object.values(trackedSyncIds).find((e) => + e.blocks.includes(key), + ); + return entry; + }; + + const handleShowSyncInfo = (syncEntry: any) => { + const time = new Date(syncEntry.timestamp).toLocaleTimeString(); + let msg = `Operation at ${time}\nStatus: ${syncEntry.status.toUpperCase()}`; + if (syncEntry.status === "pending") msg += "\nWaiting for network..."; + if (syncEntry.status === "synced") msg += "\nSuccessfully saved to cloud."; + + Alert.alert("Sync Details", msg); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const handleDuplicateBlock = async (block: any) => { + const duration = toMinutes(block.end) - toMinutes(block.start); + const nextStartMins = toMinutes(block.end); + + if (nextStartMins + duration > 24 * 60) { + Alert.alert("No Room", "Cannot duplicate block, not enough space today."); + return; + } + + const newStart = toTimeString(nextStartMins); + const newEnd = toTimeString(nextStartMins + duration); + + if (!checkTimeSlotAvailability(newStart, newEnd).available) { + Alert.alert("Collision", "Cannot duplicate here, next slot is occupied."); + return; + } + + const newBlock = { + ...block, + start: newStart, + end: newEnd, + isHabit: undefined, + }; + const nextBlocks = [...rawTimeBlocks, newBlock].sort((a, b) => + a.start.localeCompare(b.start), + ); + + const schedule = schedules?.[0]; + const syncId = await performMutation( + "schedules", + schedule ? "UPDATE" : "INSERT", + { + id: schedule?.id || Math.random().toString(36).substring(7), + user_id: schedule?.user_id || userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }, + ); + addTrackedSync(syncId, [newBlock]); + refreshSchedule(); + setActiveActionBlock(null); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + }; + + const handleMoveToTomorrow = async (block: any) => { + const tomorrow = new Date(selectedDate); + tomorrow.setDate(tomorrow.getDate() + 1); + const tomorrowStr = tomorrow.toISOString().split("T")[0]; + + // 1. Remove from today + const nextTodayBlocks = rawTimeBlocks.filter( + (b) => !(b.start === block.start && b.task === block.task), + ); + const syncId = await performMutation("schedules", "UPDATE", { + id: schedules[0].id, + user_id: userId, + date: selectedDate, + time_blocks: JSON.stringify(nextTodayBlocks), + }); + addTrackedSync(syncId, [block]); + + // 2. Add to tomorrow + const db = await getDb(); + const tomorrowSchedule = await db.getFirstAsync( + "SELECT * FROM schedules WHERE date = ? AND (user_id = ? OR user_id IS NULL)", + [tomorrowStr, userId], + ); + + const tomorrowBlocks = tomorrowSchedule + ? JSON.parse(tomorrowSchedule.time_blocks) + : []; + const newTomorrowBlocks = [ + ...tomorrowBlocks, + { ...block, isHabit: undefined }, + ].sort((a, b) => a.start.localeCompare(b.start)); + + await performMutation("schedules", tomorrowSchedule ? "UPDATE" : "INSERT", { + id: tomorrowSchedule?.id || Math.random().toString(36).substring(7), + user_id: userId, + date: tomorrowStr, + time_blocks: JSON.stringify(newTomorrowBlocks), + }); + + refreshSchedule(); + setActiveActionBlock(null); + Alert.alert("Moved", `"${block.task}" moved to tomorrow.`); + }; + + const ghostAnimatedStyle = useAnimatedStyle(() => ({ + top: ghostTop.value, + transform: [{ scale: ghostScale.value }], + })); + + const isPastTime = (timeStr: string) => { + if (selectedDate < today) return true; + if (selectedDate > today) return false; + + const now = new Date(); + const currentMins = now.getHours() * 60 + now.getMinutes(); + const targetMins = toMinutes(timeStr); + + // Allow 5 minute grace period + return targetMins < currentMins - 5; + }; + + const checkTimeSlotAvailability = ( + newStart: string, + newEnd: string, + draggedBlock?: any, + ) => { + if (isPastTime(newStart)) { + return { available: false, reason: "past" }; + } + + const startMins = toMinutes(newStart); + const endMins = toMinutes(newEnd); + const duration = endMins - startMins; + + // Find colliding block (excluding self) + const collidingBlock = combinedBlocks.find((block) => { + if ( + draggedBlock && + !draggedBlock.isNew && + block.task === draggedBlock.task && + block.start === draggedBlock.start + ) { + return false; + } + if (block.isHabit) return false; // Don't collide with habits for swaps/shifts yet + + const bStart = toMinutes(block.start); + const bEnd = toMinutes(block.end); + return startMins < bEnd && endMins > bStart; + }); + + if (!collidingBlock) { + return { available: true, reason: null }; + } + + // SWAP LOGIC: Existing block dropped on Existing block + if (draggedBlock && !draggedBlock.isNew && !collidingBlock.isHabit) { + return { + available: true, + reason: "swap", + targetBlock: collidingBlock, + }; + } + + // SHIFT LOGIC: New block dropped on Existing block + if (draggedBlock && draggedBlock.isNew) { + // Try to shift the colliding block forward + const bStart = toMinutes(collidingBlock.start); + const bEnd = toMinutes(collidingBlock.end); + const bDuration = bEnd - bStart; + + const shiftedStart = endMins; + const shiftedEnd = endMins + bDuration; + + // Check if shifted block would collide with something else or go off grid + if (shiftedEnd > 24 * 60) return { available: false, reason: "no-room" }; + + const secondaryCollision = combinedBlocks.some((block) => { + if ( + block.task === collidingBlock.task && + block.start === collidingBlock.start + ) + return false; + if (block.task === draggedBlock.task) return false; + + const sStart = toMinutes(block.start); + const sEnd = toMinutes(block.end); + return shiftedStart < sEnd && shiftedEnd > sStart; + }); + + if (secondaryCollision) { + return { available: false, reason: "no-room" }; + } + + return { + available: true, + reason: "shift", + targetBlock: collidingBlock, + shiftedTime: { + start: toTimeString(shiftedStart), + end: toTimeString(shiftedEnd), + }, + }; + } + + return { available: false, reason: "collision" }; + }; + + const { + data: schedules, + loading: schedulesLoading, + refresh: refreshSchedule, + } = useData( + "SELECT * FROM schedules WHERE date = ? AND (user_id = ? OR user_id IS NULL)", + [selectedDate, userId], + ); + + const { + data: habits, + loading: habitsLoading, + refresh: refreshHabits, + } = useData( + `SELECT h.id, h.title, h.preferred_time, h.location, + (SELECT COUNT(*) FROM logs l WHERE l.habit_id = h.id AND date(l.logged_at, 'localtime') = date('now', 'localtime')) as is_done_today + FROM habits h + WHERE h.is_active = 1 AND (h.user_id = ? OR h.user_id IS NULL)`, + [userId], + ); + + const { data: tasks } = useData<{ id: string; title: string; todos: string }>( + "SELECT id, title, todos FROM tasks WHERE (user_id = ? OR user_id IS NULL)", + [userId], + ); + + const rawTimeBlocks = useMemo(() => { + if (!schedules.length) return []; + try { + const blocks = JSON.parse(schedules[0].time_blocks) as TimeBlock[]; + return Array.isArray(blocks) ? blocks : []; + } catch { + return []; + } + }, [schedules]); + + const displayBlocks = useMemo( + () => normalizeTimeBlocks(rawTimeBlocks, sprintDuration), + [rawTimeBlocks, sprintDuration], + ); + + const combinedBlocks = useMemo(() => { + const blocks: any[] = displayBlocks.map((block) => { + // Find matching task in inventory + const linkedTask = tasks.find( + (t) => t.id === block.task_id || t.title === block.task, + ); + if (linkedTask) { + return { + ...block, + task_id: linkedTask.id, + // Use inventory todos as source of truth if available + todos: linkedTask.todos ? JSON.parse(linkedTask.todos) : block.todos, + }; + } + return block; + }); + + // Only show habits if viewing TODAY + if (selectedDate === today) { + habits.forEach((habit) => { + if (habit.preferred_time) { + const duration = 30; + const endTime = toTimeString( + toMinutes(habit.preferred_time) + duration, + ); + + blocks.push({ + start: habit.preferred_time, + end: endTime, + task: habit.title, + type: "habit", + location: habit.location, + isDone: habit.is_done_today > 0, + isHabit: true, + }); + } + }); + } + + const sorted = blocks.sort((a, b) => a.start.localeCompare(b.start)); + const layoutBlocks: any[] = []; + let currentCluster: any[] = []; + let clusterEnd = "00:00"; + + sorted.forEach((block) => { + if (block.start < clusterEnd) { + currentCluster.push(block); + if (block.end > clusterEnd) clusterEnd = block.end; + } else { + if (currentCluster.length > 0) + processCluster(currentCluster, layoutBlocks); + currentCluster = [block]; + clusterEnd = block.end; + } + }); + + if (currentCluster.length > 0) processCluster(currentCluster, layoutBlocks); + return layoutBlocks; + }, [displayBlocks, habits, selectedDate]); + + const hourHeight = useMemo(() => { + const maxCols = Math.max(1, ...combinedBlocks.map((b) => b.totalCols || 1)); + const density = combinedBlocks.length; + + if (maxCols > 1) return 220; + if (density > 10) return 180; + if (density > 5) return 140; + return 120; + }, [combinedBlocks]); + + const styles = useMemo( + () => createStyles(colors, hourHeight), + [colors, hourHeight], + ); + + const changeDay = (days: number) => { + const d = new Date(selectedDate); + d.setDate(d.getDate() + days); + setSelectedDate(d.toISOString().split("T")[0]); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const handleEditBlock = (block: any, index: number) => { + if (block.isHabit) { + router.push("/hh_habits"); + return; + } + + const originalIndex = rawTimeBlocks.findIndex( + (b) => b.start === block.start && b.task === block.task, + ); + setEditingBlockIndex(originalIndex); + setBlockTitle(block.task); + setBlockStart(block.start); + setBlockEnd(block.end); + setBlockType(block.type || "event"); + setBlockLocation(block.location || ""); + setBlockDescription(block.description || ""); + setBlockTodos(block.todos || []); + setShowModal(true); + }; + + const handleUndo = async () => { + if (!undoState) return; + + try { + const syncId = await performMutation("schedules", "UPDATE", { + id: undoState.scheduleId, + user_id: userId, + date: selectedDate, + time_blocks: JSON.stringify(undoState.blocks), + }); + addTrackedSync(syncId, undoState.blocks); + refreshSchedule(); + setUndoState(null); + setShowUndoToast(false); + if (undoTimeout.current) clearTimeout(undoTimeout.current); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + } catch (e) { + console.error("Undo failed", e); + } + }; + + const updateBlockTime = async ( + block: any, + newStart: string, + newEnd: string, + ) => { + const originalStart = block.originalStart || block.start; + let originalIndex = rawTimeBlocks.findIndex( + (b) => b.start === originalStart && b.task === block.task, + ); + + if (originalIndex === -1) { + originalIndex = rawTimeBlocks.findIndex((b) => b.task === block.task); + } + + // CAPTURE UNDO STATE + setUndoState({ blocks: [...rawTimeBlocks], scheduleId: schedules[0].id }); + + let nextBlocks = [...rawTimeBlocks]; + const updatedBlock = { + ...(originalIndex !== -1 ? nextBlocks[originalIndex] : block), + start: newStart, + end: newEnd, + originalStart: undefined, + isNew: undefined, + isHabit: undefined, + }; + + if (originalIndex !== -1) { + nextBlocks[originalIndex] = updatedBlock; + } else { + nextBlocks.push(updatedBlock); + } + + nextBlocks.sort((a, b) => a.start.localeCompare(b.start)); + + const schedule = schedules?.[0]; + try { + let syncId; + if (schedule) { + syncId = await performMutation("schedules", "UPDATE", { + id: schedule.id, + user_id: schedule.user_id || userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }); + } else { + syncId = await performMutation("schedules", "INSERT", { + id: Math.random().toString(36).substring(7), + user_id: userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }); + } + addTrackedSync(syncId, [updatedBlock]); + refreshSchedule(); + + // SHOW UNDO TOAST + setShowUndoToast(true); + if (undoTimeout.current) clearTimeout(undoTimeout.current); + undoTimeout.current = setTimeout(() => setShowUndoToast(false), 5000); + + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + console.error("Failed to update block time", e); + Alert.alert("Error", "Failed to save schedule change."); + } + }; + + const handleAddTodo = () => { + if (!newTodoText.trim()) return; + const newTodo = { + id: Math.random().toString(36).substring(7), + text: newTodoText.trim(), + completed: false, + }; + setBlockTodos([...blockTodos, newTodo]); + setNewTodoText(""); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const toggleTodo = (id: string) => { + setBlockTodos( + blockTodos.map((t) => + t.id === id ? { ...t, completed: !t.completed } : t, + ), + ); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const removeTodo = (id: string) => { + setBlockTodos(blockTodos.filter((t) => t.id !== id)); + }; + + const handleSaveBlock = async () => { + console.log("SAVE BLOCK CALLED", { + title: blockTitle, + start: blockStart, + end: blockEnd, + type: blockType, + selectedDate, + }); + if (!blockTitle.trim()) { + Alert.alert("Error", "Please enter a title for the block."); + return; + } + if (toMinutes(blockStart) >= toMinutes(blockEnd)) { + Alert.alert("Error", "Start time must be before end time."); + return; + } + + let nextBlocks = [...rawTimeBlocks]; + const newBlock: TimeBlock = { + start: blockStart, + end: blockEnd, + task: blockTitle, + type: blockType === "event" ? undefined : blockType, + location: blockLocation || undefined, + description: blockDescription || undefined, + todos: blockTodos.length > 0 ? blockTodos : undefined, + }; + + if (editingBlockIndex !== null && editingBlockIndex !== -1) { + nextBlocks[editingBlockIndex] = newBlock; + } else { + nextBlocks.push(newBlock); + } + + nextBlocks.sort((a, b) => a.start.localeCompare(b.start)); + + const schedule = schedules?.[0]; + try { + let syncId; + console.log("CALLING performMutation", { + scheduleId: schedule?.id, + time_blocks_count: nextBlocks.length, + }); + if (schedule) { + syncId = await performMutation("schedules", "UPDATE", { + id: schedule.id, + user_id: schedule.user_id || userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }); + } else { + syncId = await performMutation("schedules", "INSERT", { + id: Math.random().toString(36).substring(7), + user_id: userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }); + } + addTrackedSync(syncId, [newBlock]); + refreshSchedule(); + + // If it's a deep-work block, ensure it exists in tasks + if (blockType === "deep-work") { + const db = await getDb(); + const existingTask = await db.getFirstAsync<{ + id: string; + todos: string; + }>( + "SELECT id, todos FROM tasks WHERE title = ? AND (user_id = ? OR user_id IS NULL)", + [blockTitle.trim(), userId], + ); + + const duration = toMinutes(blockEnd) - toMinutes(blockStart); + const estimated = Math.max(1, Math.ceil(duration / sprintDuration)); + + if (existingTask) { + // Update sessions and merge todos if needed + await performMutation("tasks", "UPDATE", { + id: existingTask.id, + estimated_sessions: estimated, + todos: JSON.stringify( + blockTodos.length > 0 + ? blockTodos + : JSON.parse(existingTask.todos || "[]"), + ), + updated_at: new Date().toISOString(), + }); + } else { + // Create new task + await performMutation("tasks", "INSERT", { + id: Math.random().toString(36).substring(7), + user_id: userId, + title: blockTitle.trim(), + status: "todo", + estimated_sessions: estimated, + completed_sessions: 0, + tag: "Deep Work", + todos: JSON.stringify(blockTodos), + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }); + } + } + + setShowModal(false); + resetModal(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + console.error("Failed to save block/task", e); + Alert.alert("Error", "Failed to save schedule block."); + } + }; + + const handleDeleteBlock = async () => { + if (editingBlockIndex === null) return; + + Alert.alert("Delete Block", "Are you sure you want to remove this block?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + const blockToDelete = rawTimeBlocks[editingBlockIndex]; + const nextBlocks = rawTimeBlocks.filter( + (_, i) => i !== editingBlockIndex, + ); + const schedule = schedules[0]; + const syncId = await performMutation("schedules", "UPDATE", { + id: schedule.id, + user_id: schedule.user_id || userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }); + // Track the sync for the block that was just deleted (though it won't be rendered) + addTrackedSync(syncId, [blockToDelete]); + setShowModal(false); + resetModal(); + }, + }, + ]); + }; + + const resetModal = () => { + setEditingBlockIndex(null); + setBlockTitle(""); + setBlockStart("09:00"); + setBlockEnd("10:00"); + setBlockType("event"); + setBlockLocation(""); + setBlockDescription(""); + setBlockTodos([]); + setNewTodoText(""); + }; + + const stats = useMemo(() => { + const focusMinutes = displayBlocks + .filter((b) => b.type === "deep-work") + .reduce((acc, b) => acc + (toMinutes(b.end) - toMinutes(b.start)), 0); + + const habitTotal = habits.length; + const habitDone = habits.filter((h) => h.is_done_today > 0).length; + const adherence = + habitTotal > 0 ? Math.round((habitDone / habitTotal) * 100) : 0; + + return { + focusHours: (focusMinutes / 60).toFixed(1), + adherence: `${adherence}%`, + votes: habitDone, + }; + }, [displayBlocks, habits]); + + const upcomingTasks = useMemo(() => { + const nowMinutes = toMinutes(new Date().toTimeString().slice(0, 5)); + return combinedBlocks + .filter( + (b) => + toMinutes(b.start) >= nowMinutes && + b.type !== "break" && + b.type !== "habit", + ) + .slice(0, 3); + }, [combinedBlocks]); + + const aiSuggestion = useMemo(() => { + if (selectedDate !== today) return null; + + const nowMinutes = toMinutes(new Date().toTimeString().slice(0, 5)); + const fallenBehind = combinedBlocks.find((b) => { + const endMins = toMinutes(b.end); + const isDone = b.todos?.every((t: any) => t.completed) ?? false; + return endMins < nowMinutes && !isDone && b.type === "deep-work"; + }); + + if (fallenBehind) { + return { + message: `You missed your "${fallenBehind.task}" slot. Should I find a 15m catch-up window at the next gap?`, + type: "recovery", + action: "SCHEDULE CATCH-UP", + }; + } + return null; + }, [combinedBlocks, selectedDate, today]); + + const handleCatchUp = async () => { + // Find first gap of 15 mins + const nowMinutes = toMinutes(new Date().toTimeString().slice(0, 5)); + let current = Math.max(nowMinutes, toMinutes("09:00")); + const sorted = [...rawTimeBlocks].sort((a, b) => + a.start.localeCompare(b.start), + ); + + for (const b of sorted) { + const bStart = toMinutes(b.start); + if (bStart >= current + 20) { + break; + } + current = Math.max(current, toMinutes(b.end)); + } + + const catchUpBlock: TimeBlock = { + start: toTimeString(current + 5), + end: toTimeString(current + 20), + task: "AI Recovery: Catch-up", + type: "deep-work", + }; + + const nextBlocks = [...rawTimeBlocks, catchUpBlock].sort((a, b) => + a.start.localeCompare(b.start), + ); + + try { + const schedule = schedules[0]; + const syncId = await performMutation( + "schedules", + schedule ? "UPDATE" : "INSERT", + { + id: schedule?.id || Math.random().toString(36).substring(7), + user_id: userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }, + ); + addTrackedSync(syncId, [catchUpBlock]); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + console.error(e); + } + }; + + // Rollover Logic: Carry forward incomplete tasks from yesterday + const isRollingOver = useRef(false); + useEffect(() => { + const checkRollover = async () => { + // Only run if we are looking at TODAY and schedules are loaded + if (isRollingOver.current || schedulesLoading || selectedDate !== today) + return; + + isRollingOver.current = true; + try { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const yesterdayStr = yesterday.toISOString().split("T")[0]; + + const db = await getDb(); + + // Check if we've already rolled over today (prevent duplicates) + const todaySchedule = schedules[0]; + const todayBlocks: TimeBlock[] = todaySchedule + ? JSON.parse(todaySchedule.time_blocks) + : []; + const alreadyCarried = todayBlocks.some((b) => + b.task.includes("[CARRIED]"), + ); + + if (alreadyCarried) return; + + // Fetch yesterday's schedule + const prevSchedule = await db.getFirstAsync( + "SELECT * FROM schedules WHERE date = ? AND (user_id = ? OR user_id IS NULL)", + [yesterdayStr, userId], + ); + + if (prevSchedule) { + const prevBlocks = JSON.parse( + prevSchedule.time_blocks, + ) as TimeBlock[]; + + // Find incomplete deep-work or event blocks + const incompleteBlocks = prevBlocks + .filter((b) => { + const hasIncompleteTodos = + b.todos && b.todos.some((t) => !t.completed); + const isDeepWork = b.type === "deep-work"; + return (hasIncompleteTodos || isDeepWork) && b.task !== "Break"; + }) + .map((b) => ({ + ...b, + task: `[CARRIED] ${b.task.replace("[CARRIED] ", "")}`, + // Move to a morning slot today + start: "09:00", + end: "10:00", + })); + + if (incompleteBlocks.length > 0) { + const updatedBlocks = [...todayBlocks, ...incompleteBlocks].sort( + (a, b) => a.start.localeCompare(b.start), + ); + + await performMutation( + "schedules", + todaySchedule ? "UPDATE" : "INSERT", + { + id: + todaySchedule?.id || Math.random().toString(36).substring(7), + user_id: userId, + date: today, + time_blocks: JSON.stringify(updatedBlocks), + }, + ); + + refreshSchedule(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + Alert.alert( + "Tasks Carried Over", + `Carried forward ${incompleteBlocks.length} items from yesterday.`, + ); + } + } + } catch (e) { + console.error("Rollover failed", e); + } + }; + + checkRollover(); + }, [schedules.length, schedulesLoading, selectedDate, today]); + + return ( + + + + + router.push("/menu")} + > + + + + + + + { + setIsMultiSelectMode(!isMultiSelectMode); + if (!isMultiSelectMode) setSelectedBlockKeys([]); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + + router.push("/history")} + > + + + router.push("/modal")} + > + + + + + + + + { + scrollY.current = e.nativeEvent.contentOffset.y; + }} + > + + + + {selectedDate === today + ? "TODAY" + : new Date(selectedDate) + .toLocaleDateString("en-US", { weekday: "long" }) + .toUpperCase()} + + + {new Date(selectedDate).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + })} + + + + + + {stats.adherence} + + + + {stats.votes} + + + + {stats.focusHours}h + + + + + {/* AI Suggestion Banner */} + {aiSuggestion && ( + + + + + + {aiSuggestion.message} + + + + {aiSuggestion.action} + + + + + + )} + + {/* Upcoming Tasks Summary */} + {upcomingTasks.length > 0 && ( + + NEXT UP + + {upcomingTasks.map((task, i) => ( + handleEditBlock(task, -1)} + > + + + {task.start} + + + {task.task} + + + ))} + + + )} + + {/* Task Inventory Section */} + + + TASK INVENTORY + router.push("/add-task")}> + + + + + {tasks + .filter((t) => !rawTimeBlocks.some((b) => b.task_id === t.id)) + .map((task, i) => ( + { + triggerHaptic("light"); + setActiveActionBlock(null); + }} + > + + {task.title} + + + ))} + {tasks.filter( + (t) => !rawTimeBlocks.some((b) => b.task_id === t.id), + ).length === 0 && ( + + No unscheduled tasks + + )} + + + + + + {TIME_MARKERS.map((time, index) => ( + + {time} + + ))} + + + { + triggerHaptic("medium"); + measureTimeline(); // CRITICAL: Re-measure on entry to account for scrolls/keyboard + }} + onReceiveDragOver={(event) => { + const absoluteY = + (event as any)?.absoluteY || + (event as any)?.pageY || + (event as any)?.nativeEvent?.pageY || + 0; + const relativeY = + absoluteY - timelineLayout.y + scrollY.current; + + console.log("DRAG OVER", { + absoluteY, + timelineTop: timelineLayout.y, + scrollY: scrollY.current, + relativeY, + receiverY: event?.receiverLocation?.y, + }); + + const findY = (obj: any, depth = 0): any => { + if (depth > 3 || !obj || typeof obj !== "object") + return null; + try { + for (const key of Object.keys(obj)) { + if (key.toLowerCase().includes("y")) { + console.log(`Found y-like key: ${key} = ${obj[key]}`); + } + findY(obj[key], depth + 1); + } + } catch (e) {} + }; + // findY(event); // Uncomment if deeper property search is needed + + const payload = event.dragged?.payload as any; + const dropY = + typeof event?.receiverLocation?.y === "number" + ? event.receiverLocation.y + : relativeY > 0 + ? relativeY + : (lastDragY.current ?? 0); + + if (payload && typeof dropY === "number") { + lastDragY.current = dropY; + const minutes = + Math.floor(((dropY / hourHeight) * 60) / 15) * 15; + const clampedMinutes = Math.max( + 0, + Math.min(23 * 60, minutes), + ); + const newStartStr = toTimeString(clampedMinutes); + + // MAGNETIC SNAP & PULSE + const targetTop = getTimeOffset(newStartStr, hourHeight); + ghostTop.value = withSpring(targetTop, { + damping: 20, + stiffness: 200, + }); + + if (clampedMinutes !== lastSnap.current) { + lastSnap.current = clampedMinutes; + ghostScale.value = withSequence( + withSpring(1.02, { damping: 10, stiffness: 400 }), + withSpring(1.0, { damping: 10, stiffness: 400 }), + ); + triggerHaptic("selection"); + } + + // AUTO-SCROLL LOGIC + if (scrollInterval.current) + clearInterval(scrollInterval.current); + + const scrollZoneHeight = 100; + // For edge detection, we use screen-relative coords + const screenY = absoluteY - timelineLayout.y; + const viewportHeight = 600; + + if (screenY < scrollZoneHeight) { + const speed = Math.max( + 2, + (scrollZoneHeight - screenY) / 5, + ); + scrollInterval.current = setInterval(() => { + const nextScroll = Math.max(0, scrollY.current - speed); + scrollRef.current?.scrollTo({ + y: nextScroll, + animated: false, + }); + }, 16); + } else if (screenY > viewportHeight - scrollZoneHeight) { + const speed = Math.max( + 2, + (screenY - (viewportHeight - scrollZoneHeight)) / 5, + ); + scrollInterval.current = setInterval(() => { + const nextScroll = Math.min( + hourHeight * 24, + scrollY.current + speed, + ); + scrollRef.current?.scrollTo({ + y: nextScroll, + animated: false, + }); + }, 16); + } + + // MULTI-BLOCK PREVIEW + if (payload.isMulti) { + const masterOrigStart = toMinutes(payload.start); + const delta = clampedMinutes - masterOrigStart; + + const shiftedBlocks = getSelectedBlocks().map((b) => ({ + ...b, + start: toTimeString(toMinutes(b.start) + delta), + end: toTimeString(toMinutes(b.end) + delta), + })); + + setHoverBlock({ + isMulti: true, + blocks: shiftedBlocks, + delta, + }); + } else { + let duration = 60; + if (payload.isNew) { + const tag = payload.tag?.toLowerCase() || ""; + if (tag.includes("deep") || tag.includes("work")) + duration = sprintDuration; + else if (tag.includes("break")) duration = 15; + else if ( + tag.includes("meeting") || + tag.includes("call") + ) + duration = 30; + else if (tag.includes("chore") || tag.includes("task")) + duration = 30; + } else { + duration = + toMinutes(payload.end) - toMinutes(payload.start); + } + setHoverBlock({ + ...payload, + start: newStartStr, + end: toTimeString(clampedMinutes + duration), + }); + } + } + }} + onReceiveDragExit={() => { + setHoverBlock(null); + lastSnap.current = -1; + // DO NOT clear lastDragY here, we need it as fallback for drop + triggerHaptic("warning"); + if (scrollInterval.current) { + clearInterval(scrollInterval.current); + scrollInterval.current = null; + } + }} + onReceiveDragDrop={async (event) => { + const payload = event.dragged?.payload as any; + const absoluteY = + (event as any)?.absoluteY || + (event as any)?.pageY || + (event as any)?.nativeEvent?.pageY || + 0; + const relativeY = + absoluteY - timelineLayout.y + scrollY.current; + + const dropY = + typeof event?.receiverLocation?.y === "number" + ? event.receiverLocation.y + : relativeY > 0 + ? relativeY + : (lastDragY.current ?? 0); + + console.log("DROP COORDINATE RECOVERY FINAL", { + actualY: event?.receiverLocation?.y, + relativeY, + cachedY: lastDragY.current, + finalY: dropY, + }); + + setHoverBlock(null); + lastSnap.current = -1; + lastDragY.current = null; + if (scrollInterval.current) { + clearInterval(scrollInterval.current); + scrollInterval.current = null; + } + + if (payload && typeof dropY === "number") { + const minutes = + Math.floor(((dropY / hourHeight) * 60) / 15) * 15; + const clampedMinutes = Math.max( + 0, + Math.min(23 * 60, minutes), + ); + const newStart = toTimeString(clampedMinutes); + console.log("TIME CALCULATION", { + clampedMinutes, + newStart, + }); + + if (payload.isMulti) { + const masterOrigStart = toMinutes(payload.start); + const delta = clampedMinutes - masterOrigStart; + + let nextBlocks = [...rawTimeBlocks]; + let isValid = true; + + const selectedBlocks = getSelectedBlocks(); + const updatedSelected = selectedBlocks.map((b) => { + const newS = toTimeString(toMinutes(b.start) + delta); + const newE = toTimeString(toMinutes(b.end) + delta); + + // Validation + if (isPastTime(newS)) isValid = false; + if (toMinutes(newE) > 24 * 60) isValid = false; + + // Collision check against NON-selected blocks + const collision = rawTimeBlocks.some((rb) => { + const isCurrentlySelected = + selectedBlockKeys.includes( + `${rb.start}-${rb.task}`, + ); + if (isCurrentlySelected) return false; + + const rbStart = toMinutes(rb.start); + const rbEnd = toMinutes(rb.end); + return ( + toMinutes(newS) < rbEnd && toMinutes(newE) > rbStart + ); + }); + + if (collision) isValid = false; + + return { ...b, start: newS, end: newE }; + }); + + if (!isValid) { + triggerHaptic("error"); + setDropCollision(true); + setTimeout(() => setDropCollision(false), 500); + return; + } + + // Apply updates + updatedSelected.forEach((ub) => { + const idx = nextBlocks.findIndex( + (nb) => + nb.task === ub.task && + nb.start === ub.originalStart, + ); + if (idx !== -1) { + nextBlocks[idx] = { + ...nextBlocks[idx], + start: ub.start, + end: ub.end, + }; + } + }); + + const schedule = schedules[0]; + const syncId = await performMutation( + "schedules", + "UPDATE", + { + id: schedule.id, + user_id: schedule.user_id || userId, + date: selectedDate, + time_blocks: JSON.stringify(nextBlocks), + }, + ); + addTrackedSync(syncId, updatedSelected); + + refreshSchedule(); + setIsMultiSelectMode(false); + setSelectedBlockKeys([]); + triggerHaptic("success"); + return; + } + + // SINGLE DROP LOGIC + // ... (duration logic) + let duration = 60; + if (payload.isNew) { + const tag = payload.tag?.toLowerCase() || ""; + if (tag.includes("deep") || tag.includes("work")) + duration = sprintDuration; + else if (tag.includes("break")) duration = 15; + else if (tag.includes("meeting") || tag.includes("call")) + duration = 30; + else if (tag.includes("chore") || tag.includes("task")) + duration = 30; + } else { + duration = + toMinutes(payload.end) - toMinutes(payload.start); + } + const newEnd = toTimeString(clampedMinutes + duration); + + const checkResult = checkTimeSlotAvailability( + newStart, + newEnd, + payload, + ); + if (!checkResult.available) { + // ... rejection logic + triggerHaptic("error"); + setDropCollision(true); + setTimeout(() => setDropCollision(false), 500); + if (checkResult.reason === "past") + Alert.alert( + "Temporal Lock", + "Cannot schedule tasks in the past.", + ); + return; + } + + // ... swap/shift logic ... + + if (payload.isNew) { + console.log("NEW TASK PATH", { + taskTitle: payload.title, + calculatedStart: newStart, + }); + setBlockTitle(payload.title); + setBlockStart(newStart); + setBlockEnd(newEnd); + + const tag = payload.tag?.toLowerCase() || ""; + if (tag.includes("deep") || tag.includes("work")) { + setBlockType("deep-work"); + } else if (tag.includes("break")) { + setBlockType("break"); + } else { + setBlockType("event"); + } + + setEditingBlockIndex(null); + console.log("SETTING MODAL STATE", { + showModal: true, + blockTitle: payload.title, + blockStart: newStart, + }); + setShowModal(true); + } else { + await updateBlockTime(payload, newStart, newEnd); + } + triggerHaptic("success"); + } + }} + > + + {TIME_MARKERS.map((_, index) => ( + + ))} + + + {/* PAST TIME OVERLAY */} + {selectedDate <= today && ( + + )} + + {hoverBlock && !hoverBlock.isMulti && ( + + + {hoverBlock.task || hoverBlock.title} + + + )} + + {hoverBlock && + hoverBlock.isMulti && + hoverBlock.blocks.map((b: any, i: number) => ( + + {b.task} + + ))} + + {combinedBlocks.map((block, index) => { + const top = getTimeOffset(block.start, hourHeight); + const height = getBlockHeight( + block.start, + block.end, + hourHeight, + ); + const blockStyle = + block.type === "deep-work" + ? styles.deepWorkBlock + : block.type === "break" + ? styles.breakBlock + : block.type === "habit" + ? [ + styles.habitBlock, + block.isDone && { opacity: 0.6 }, + ] + : styles.defaultBlock; + + const completedTodos = + block.todos?.filter((t: any) => t.completed).length || 0; + const totalTodos = block.todos?.length || 0; + + const isSelected = selectedBlockKeys.includes( + `${block.start}-${block.task}`, + ); + const syncStatus = getSyncStatusForBlock(block); + + return ( + { + triggerHaptic("light"); + setActiveActionBlock(null); + }} + longPressDelay={ + isMultiSelectMode && isSelected ? 200 : 500 + } + > + { + if (isMultiSelectMode) { + toggleBlockSelection(block); + } else if (activeActionBlock) { + setActiveActionBlock(null); + } else { + handleEditBlock(block, index); + } + }} + onLongPress={() => { + if (isMultiSelectMode) return; // Drax handles drag + if (!block.isHabit) { + setActiveActionBlock(block); + triggerHaptic("medium"); + } + }} + style={{ flex: 1 }} + > + + + + {block.type?.toUpperCase() || "EVENT"} + + + {block.task} + + + + {syncStatus && ( + handleShowSyncInfo(syncStatus)} + > + {syncStatus.status === "pending" && ( + + )} + {syncStatus.status === "synced" && ( + + )} + {syncStatus.status === "failed" && ( + + )} + + )} + {isSelected && ( + + + + )} + + + + {totalTodos > 0 && ( + + + {completedTodos}/{totalTodos} points + + + )} + + {block.description && ( + + • {block.description} + + )} + + + {/* QUICK ACTIONS OVERLAY */} + {activeActionBlock?.start === block.start && + activeActionBlock?.task === block.task && ( + + handleDuplicateBlock(block)} + > + + Copy + + handleMoveToTomorrow(block)} + > + + +1 Day + + { + setEditingBlockIndex( + rawTimeBlocks.findIndex( + (b) => + b.start === block.start && + b.task === block.task, + ), + ); + handleDeleteBlock(); + }} + > + + + Del + + + setActiveActionBlock(null)} + > + + + + )} + + ); + })} + + {selectedDate === today && ( + + + + + )} + + + + + + + + + { + setShowModal(false); + resetModal(); + }} + style={styles.modalTopBtn} + > + + Cancel + + + + + {editingBlockIndex !== null ? "Edit Block" : "Add Block"} + + + + + + Save + + + + + + {editingBlockIndex !== null && ( + + + + Delete this block + + + )} + + WHAT ARE YOU DOING? + + + + + + + + + + + + + + BLOCK TYPE + + + {[ + { id: "event", label: "Event", icon: CalendarIcon }, + { id: "deep-work", label: "Deep Work", icon: Zap }, + { id: "chores", label: "Chores", icon: Target }, + { id: "walk", label: "Walk", icon: MapPin }, + { id: "break", label: "Break", icon: Clock }, + ].map((item) => { + const isSelected = blockType === item.id; + const IconComp = item.icon; + return ( + setBlockType(item.id as any)} + > + + + {item.label} + + + ); + })} + + + + + + SUB-TASKS (POINTS) + + + + + + + + {blockTodos.map((todo) => ( + + toggleTodo(todo.id)} + style={[ + styles.todoCheck, + { borderColor: colors.primary }, + todo.completed && { + backgroundColor: colors.primary, + }, + ]} + > + {todo.completed && ( + + )} + + + {todo.text} + + removeTodo(todo.id)}> + + + + ))} + + + + + NOTES + + + + + LOCATION + + + + + + + + { + resetModal(); + setShowModal(true); + }} + > + + + + {/* UNDO TOAST */} + {showUndoToast && ( + + Block moved + + UNDO + + + )} + + + + ); +} + +const createStyles = (colors: any, hourHeight: number) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + scrollView: { + flex: 1, + }, + contentContainer: { + paddingBottom: 100, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.background, + height: 60, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + menuBtn: { + padding: 8, + }, + logoImage: { + height: 40, + width: 160, + }, + ghostBtn: { + padding: 8, + }, + headerRight: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + dateHeader: { + padding: SPACING.lg, + backgroundColor: colors.surface, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + dateInfo: { + flex: 1, + }, + dayNavRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + marginBottom: 2, + }, + navBtn: { + padding: 4, + backgroundColor: colors.primary + "1A", + borderRadius: 4, + }, + dayLabel: { + fontFamily: FONTS.labelSm, + fontSize: 12, + color: colors.primary, + letterSpacing: 1.5, + }, + dateLabel: { + fontFamily: FONTS.headline, + fontSize: 32, + color: colors.onSurface, + }, + metricsGrid: { + flexDirection: "row", + gap: 12, + }, + metricItem: { + flexDirection: "row", + alignItems: "center", + gap: 4, + backgroundColor: colors.surfaceVariant + "80", + paddingHorizontal: 8, + paddingVertical: 6, + borderRadius: ROUNDNESS.sm, + }, + metricValue: { + fontFamily: FONTS.labelSm, + fontSize: 12, + color: colors.onSurface, + }, + timelineWrapper: { + flexDirection: "row", + paddingRight: SPACING.lg, + }, + timeColumn: { + width: 70, + }, + timeSlot: { + height: hourHeight, + alignItems: "center", + justifyContent: "flex-start", + paddingTop: 10, + }, + timeText: { + fontFamily: FONTS.label, + fontSize: 11, + color: colors.onSurfaceVariant, + }, + calendarGrid: { + flex: 1, + position: "relative", + minHeight: hourHeight * 24, + }, + gridLines: { + ...StyleSheet.absoluteFillObject, + }, + gridLine: { + height: hourHeight, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "33", + }, + taskBlock: { + position: "absolute", + left: 0, + right: 0, + padding: 12, + borderRadius: ROUNDNESS.md, + justifyContent: "space-between", + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + marginRight: 4, + }, + blockLabel: { + fontFamily: FONTS.labelSm, + fontSize: 9, + letterSpacing: 1, + marginBottom: 2, + }, + taskTitle: { + fontFamily: FONTS.headline, + fontSize: 16, + }, + defaultBlock: { + backgroundColor: colors.surface, + }, + deepWorkBlock: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + breakBlock: { + backgroundColor: colors.surfaceVariant, + borderLeftWidth: 3, + borderLeftColor: colors.tertiary, + }, + habitBlock: { + backgroundColor: colors.primaryContainer + "40", + borderLeftWidth: 4, + borderLeftColor: colors.primary, + }, + choresBlock: { + backgroundColor: colors.secondaryContainer + "40", + borderLeftWidth: 4, + borderLeftColor: colors.secondary, + }, + taskLocation: { + fontFamily: FONTS.body, + fontSize: 10, + marginTop: 2, + }, + todoProgress: { + marginTop: 4, + }, + todoText: { + fontFamily: FONTS.label, + fontSize: 10, + }, + currentTimeLine: { + position: "absolute", + left: -5, + right: 0, + height: 2, + flexDirection: "row", + alignItems: "center", + zIndex: 10, + }, + timeDot: { + width: 10, + height: 10, + borderRadius: 5, + }, + line: { + flex: 1, + height: 2, + }, + fab: { + position: "absolute", + bottom: 80, + right: 24, + width: 56, + height: 56, + borderRadius: 28, + alignItems: "center", + justifyContent: "center", + elevation: 4, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.35)", + justifyContent: "flex-end", + }, + modalContainer: { + backgroundColor: colors.surface, + padding: SPACING.lg, + borderTopLeftRadius: ROUNDNESS.xl, + borderTopRightRadius: ROUNDNESS.xl, + maxHeight: "90%", + }, + modalHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: SPACING.xl, + paddingBottom: SPACING.md, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "33", + }, + modalTopBtn: { + paddingVertical: 8, + paddingHorizontal: 12, + }, + modalSaveTopBtn: { + flexDirection: "row", + alignItems: "center", + gap: 6, + paddingVertical: 8, + paddingHorizontal: 16, + borderRadius: ROUNDNESS.full, + }, + modalTitle: { + fontFamily: FONTS.headline, + fontSize: 18, + color: colors.onSurface, + }, + deleteBtnInline: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + paddingVertical: 12, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderStyle: "dashed", + marginBottom: SPACING.lg, + }, + deleteBtnText: { + fontFamily: FONTS.labelSm, + fontSize: 13, + }, + modalField: { + marginBottom: SPACING.md, + }, + modalFieldRow: { + flexDirection: "row", + gap: 12, + marginBottom: SPACING.md, + }, + modalFieldSmall: { + flex: 1, + }, + modalLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.onSurfaceVariant, + marginBottom: 6, + }, + modalInput: { + backgroundColor: colors.surfaceVariant, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + paddingVertical: 12, + paddingHorizontal: 12, + color: colors.onSurface, + fontFamily: FONTS.body, + fontSize: 14, + }, + timeAdjustmentRow: { + flexDirection: "row", + gap: 12, + marginBottom: SPACING.md, + }, + timeField: { + flex: 1, + }, + modalTypeRow: { + flexDirection: "row", + marginTop: 4, + }, + typeChip: { + flexDirection: "row", + alignItems: "center", + gap: 6, + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: ROUNDNESS.full, + backgroundColor: colors.surfaceVariant + "4D", + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + typeChipText: { + fontFamily: FONTS.labelSm, + fontSize: 13, + color: colors.onSurfaceVariant, + }, + todoInputRow: { + flexDirection: "row", + gap: 8, + marginBottom: 12, + }, + addTodoBtn: { + width: 48, + height: 48, + borderRadius: ROUNDNESS.sm, + alignItems: "center", + justifyContent: "center", + }, + todoList: { + gap: 8, + }, + todoItem: { + flexDirection: "row", + alignItems: "center", + gap: 12, + backgroundColor: colors.surfaceVariant + "4D", + padding: 10, + borderRadius: ROUNDNESS.sm, + }, + todoCheck: { + width: 20, + height: 20, + borderRadius: 4, + borderWidth: 2, + alignItems: "center", + justifyContent: "center", + }, + todoLabel: { + flex: 1, + fontFamily: FONTS.body, + fontSize: 14, + }, + modalCancelText: { + fontFamily: FONTS.label, + color: colors.onSurface, + }, + modalSaveText: { + fontFamily: FONTS.label, + color: colors.onPrimary, + }, + center: { + justifyContent: "center", + alignItems: "center", + }, + emptyCalendar: { + flex: 1, + justifyContent: "center", + alignItems: "center", + paddingTop: 100, + }, + emptyText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + }, + aiNudgeContainer: { + paddingHorizontal: SPACING.lg, + marginBottom: SPACING.md, + }, + aiNudgeCard: { + flexDirection: "row", + alignItems: "center", + padding: 16, + borderRadius: ROUNDNESS.lg, + gap: 12, + }, + aiNudgeText: { + fontFamily: FONTS.body, + fontSize: 13, + color: colors.onSurface, + lineHeight: 18, + }, + aiNudgeAction: { + marginTop: 8, + }, + aiNudgeActionText: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1, + }, + upcomingSection: { + paddingHorizontal: SPACING.lg, + marginBottom: SPACING.lg, + }, + upcomingRow: { + gap: 12, + paddingRight: SPACING.lg, + }, + upcomingCard: { + backgroundColor: colors.surface, + padding: 12, + borderRadius: ROUNDNESS.md, + width: 140, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + upcomingTimeRow: { + flexDirection: "row", + alignItems: "center", + gap: 4, + marginBottom: 4, + }, + upcomingTime: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.primary, + }, + upcomingTitle: { + fontFamily: FONTS.labelSm, + fontSize: 13, + color: colors.onSurface, + }, + ghostBlock: { + position: "absolute", + left: 0, + right: 0, + borderWidth: 2, + borderStyle: "dashed", + borderColor: colors.primary, + opacity: 0.6, + zIndex: 50, + justifyContent: "center", + alignItems: "center", + }, + ghostText: { + fontFamily: FONTS.labelSm, + fontSize: 12, + color: colors.primary, + fontWeight: "bold", + }, + inventorySection: { + paddingHorizontal: SPACING.lg, + marginBottom: SPACING.lg, + }, + inventoryRow: { + gap: 12, + paddingRight: SPACING.lg, + paddingVertical: 8, + }, + inventoryCard: { + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: ROUNDNESS.full, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + minWidth: 100, + alignItems: "center", + justifyContent: "center", + }, + inventoryTitle: { + fontFamily: FONTS.labelSm, + fontSize: 12, + color: colors.onSurface, + }, + emptyInventoryText: { + fontFamily: FONTS.body, + fontSize: 12, + color: colors.onSurfaceVariant, + fontStyle: "italic", + marginTop: 8, + }, + quickActionsContainer: { + position: "absolute", + top: -45, + left: 0, + right: 0, + flexDirection: "row", + justifyContent: "center", + gap: 8, + zIndex: 100, + backgroundColor: colors.surface, + padding: 6, + borderRadius: ROUNDNESS.full, + elevation: 4, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + quickActionBtn: { + flexDirection: "row", + alignItems: "center", + gap: 4, + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: ROUNDNESS.full, + backgroundColor: colors.surfaceVariant + "4D", + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + quickActionText: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + }, + undoToast: { + position: "absolute", + bottom: 100, + left: 20, + right: 20, + backgroundColor: colors.onSurface, + borderRadius: ROUNDNESS.md, + padding: 16, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + elevation: 6, + shadowColor: "#000", + shadowOffset: { width: 0, height: 3 }, + shadowOpacity: 0.3, + shadowRadius: 5, + zIndex: 1000, + }, + undoToastText: { + color: colors.surface, + fontFamily: FONTS.body, + fontSize: 14, + }, + undoBtn: { + paddingHorizontal: 12, + paddingVertical: 6, + }, + undoBtnText: { + color: colors.primaryContainer, + fontFamily: FONTS.label, + fontSize: 14, + fontWeight: "bold", + }, + }); diff --git a/src/app/(tabs)/hh_habits.tsx b/src/app/(tabs)/hh_habits.tsx new file mode 100644 index 0000000..af5212a --- /dev/null +++ b/src/app/(tabs)/hh_habits.tsx @@ -0,0 +1,1081 @@ +import AutoHideScrollView from "@/src/components/AutoHideScrollView"; +import { CalendarStrip } from "@/src/components/ui/CalendarStrip"; +import { TimeInput } from "@/src/components/ui/TimeInput"; +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useData } from "@/src/hooks/useData"; +import { useTheme } from "@/src/hooks/useTheme"; +import { getLocalDateString } from "@/src/lib/date-utils"; +import { recalculateAllStreaks } from "@/src/lib/habit-logic"; +import { performMutation } from "@/src/lib/sync"; +import * as Haptics from "expo-haptics"; +import { useFocusEffect, useRouter } from "expo-router"; +import { + Calendar as CalendarIcon, + Check, + ChevronRight, + Clock, + Flame, + History, + MapPin, + Menu, + PlusCircle, + Save, + Settings, + Sparkles, + Trash2, + X, +} from "lucide-react-native"; +import React, { useCallback, useMemo, useState } from "react"; +import { + ActivityIndicator, + Alert, + Image, + KeyboardAvoidingView, + Modal, + Platform, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +interface Habit { + id: string; + title: string; + frequency: string; + is_active: number; + is_done_today: number; + is_done_yesterday: number; + current_streak: number; + max_streak: number; + preferred_time: string; + location: string; + two_minute_version: string; +} + +export default function HabitsScreen() { + const { colors, identityAnchor } = useTheme(); + const { user } = useAuth(); + const router = useRouter(); + const styles = useMemo(() => createStyles(colors), [colors]); + + const userId = user?.id || "guest"; + const today = getLocalDateString(); + const [selectedDate, setSelectedDate] = useState(today); + + const { + data: habits, + loading, + refresh, + } = useData( + `SELECT h.*, + (SELECT COUNT(*) FROM logs l WHERE l.habit_id = h.id AND date(l.logged_at, 'localtime') = ?) as is_done_today, + (SELECT COUNT(*) FROM logs l WHERE l.habit_id = h.id AND date(l.logged_at, 'localtime') = date(?, '-1 day')) as is_done_yesterday + FROM habits h + WHERE h.is_active = 1 AND (h.user_id = ? OR h.user_id IS NULL) + ORDER BY h.preferred_time ASC`, + [selectedDate, selectedDate, userId], + ); + + const { data: matrixLogs } = useData<{ date: string; count: number }>( + "SELECT date(logged_at, 'localtime') as date, COUNT(*) as count FROM logs GROUP BY date ORDER BY date DESC LIMIT 28", + [], + ); + + const matrixCells = useMemo(() => { + const cells = []; + const habitCount = habits.length || 1; + for (let i = 27; i >= 0; i--) { + const d = new Date(); + d.setDate(d.getDate() - i); + const dateStr = getLocalDateString(d); + + const logEntry = matrixLogs.find((l) => l.date === dateStr); + const completionCount = logEntry ? logEntry.count : 0; + const intensity = Math.min(completionCount / habitCount, 1); + + cells.push({ + date: dateStr, + intensity, + isToday: i === 0, + }); + } + return cells; + }, [matrixLogs, habits.length]); + + const [editingHabit, setEditingHabit] = useState(null); + const [editTitle, setEditTitle] = useState(""); + const [editTime, setEditTime] = useState(""); + const [editLocation, setEditLocation] = useState(""); + const [editGateway, setEditGateway] = useState(""); + + const dailySummary = useMemo(() => { + const total = habits.length; + const completed = habits.filter((h) => h.is_done_today > 0).length; + const percentage = total > 0 ? Math.round((completed / total) * 100) : 0; + return { total, completed, percentage }; + }, [habits]); + + const aiNudge = useMemo(() => { + const fallingOff = habits.find( + (h) => + h.current_streak > 0 && + h.is_done_today === 0 && + h.is_done_yesterday === 0, + ); + if (fallingOff) { + return { + message: `Your ${fallingOff.current_streak}-day streak for "${fallingOff.title}" is at risk. Use your gateway version: "${fallingOff.two_minute_version || "Just start for 2 minutes"}" to keep it alive!`, + type: "warning", + }; + } + if (dailySummary.percentage === 100 && habits.length > 0) { + return { + message: + "Perfect day! Every action is a vote for the person you wish to become.", + type: "success", + }; + } + return null; + }, [habits, dailySummary]); + + useFocusEffect( + useCallback(() => { + recalculateAllStreaks(userId).then(() => { + refresh(); + }); + }, [userId, refresh]), + ); + + const handleToggleHabit = async (habit: Habit) => { + try { + const isDone = habit.is_done_today > 0; + if (isDone) { + const db = await (await import("@/src/db/database")).getDb(); + await db.runAsync( + "DELETE FROM logs WHERE habit_id = ? AND date(logged_at, 'localtime') = date('now', 'localtime')", + [habit.id], + ); + } else { + await performMutation("logs", "INSERT", { + id: Math.random().toString(36).substring(7), + habit_id: habit.id, + status: "completed", + logged_at: new Date().toISOString(), + }); + } + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + refresh(); + } catch (err) { + console.error("Failed to log habit:", err); + } + }; + + const handleOpenEdit = (habit: Habit) => { + setEditingHabit(habit); + setEditTitle(habit.title); + setEditTime(habit.preferred_time || ""); + setEditLocation(habit.location || ""); + setEditGateway(habit.two_minute_version || ""); + }; + + const handleSaveEdit = async () => { + if (!editingHabit || !editTitle.trim()) return; + + try { + await performMutation("habits", "UPDATE", { + id: editingHabit.id, + title: editTitle, + preferred_time: editTime, + location: editLocation, + two_minute_version: editGateway, + updated_at: new Date().toISOString(), + }); + setEditingHabit(null); + refresh(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + Alert.alert("Error", "Failed to update habit"); + } + }; + + const backfillYesterday = async () => { + if (!editingHabit) return; + try { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + + await performMutation("logs", "INSERT", { + id: Math.random().toString(36).substring(7), + habit_id: editingHabit.id, + status: "completed", + logged_at: yesterday.toISOString(), + }); + Alert.alert("Success", "Logged for yesterday."); + refresh(); + } catch (e) { + console.error(e); + } + }; + + const handleDeleteHabit = async (id: string) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + Alert.alert("Delete Habit", "Are you sure you want to remove this habit?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + try { + await performMutation("habits", "DELETE", { id }); + setEditingHabit(null); + refresh(); + } catch (error) { + Alert.alert("Error", "Failed to delete habit"); + } + }, + }, + ]); + }; + + if (loading && habits.length === 0) { + return ( + + + + ); + } + + return ( + + + {/* Header */} + + router.push("/menu")} + > + + + + + + + + router.push("/modal")} + > + + + + + + + + {/* Identity Header */} + + ATOMIC PRINCIPLES + {identityAnchor} + + + {/* Consistency Matrix */} + + + CONSISTENCY MATRIX (28 DAYS) + + + + {matrixCells.map((cell, idx) => ( + 0 + ? colors.primary + : colors.surfaceVariant, + opacity: + cell.intensity > 0 ? 0.3 + cell.intensity * 0.7 : 1, + borderColor: cell.isToday + ? colors.primary + : "transparent", + borderWidth: cell.isToday ? 1.5 : 0, + }, + ]} + /> + ))} + + + LESS + + + + MORE + + + + + {/* Daily Summary Card */} + + + + Progress + + {dailySummary.completed} of {dailySummary.total} habits + completed + + + + + {dailySummary.percentage}% + + + + + + + + + {/* AI Nudge Section */} + {aiNudge && ( + + + + {aiNudge.message} + + + )} + + {/* Habits List */} + + + Daily Votes + router.push("/add-habit")} + > + + NEW HABIT + + + + + {habits.map((habit) => { + const isDone = habit.is_done_today > 0; + const isRecovery = habit.is_done_yesterday === 0 && !isDone; + const streakProgress = Math.min( + (habit.current_streak / 30) * 100, + 100, + ); + + return ( + + handleToggleHabit(habit)} + onLongPress={() => handleOpenEdit(habit)} + > + + + {isDone && ( + + )} + + + + + + {habit.title} + + {isRecovery && ( + + + NEVER MISS TWICE + + + )} + + + + + + + {habit.preferred_time || "08:00"} + + + + 0 + ? "#FF6B00" + : colors.outline + } + /> + 0 && { + color: "#FF6B00", + fontFamily: FONTS.labelSm, + }, + ]} + > + {habit.current_streak} DAY STREAK + + + + + + handleOpenEdit(habit)} + style={styles.settingsBtn} + > + + + + + {/* Streak Progress Bar */} + + 0 + ? "#FF6B00" + : colors.outlineVariant, + }, + ]} + /> + + + + ); + })} + + + + {/* Detailed Matrix Shortcut */} + router.push("/calendar")} + > + + + View detailed consistency matrix + + + + + + {/* Edit Habit Modal */} + setEditingHabit(null)} + > + + + + Adjust Habit + setEditingHabit(null)}> + + + + + + + TITLE + + + + + + + + + LOCATION / ANCHOR + + + + + + + + GATEWAY VERSION (2 MIN) + + + + + MISSED A DAY? + + + + Mark completed for yesterday + + + + + + + handleDeleteHabit(editingHabit!.id)} + > + + + + + + Save Changes + + + + + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + scrollView: { + flex: 1, + }, + contentContainer: { + paddingBottom: 40, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.background, + height: 60, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + menuBtn: { + padding: 8, + }, + logoImage: { + height: 40, + width: 160, + }, + ghostBtn: { + padding: 8, + }, + identityHeader: { + padding: SPACING.lg, + backgroundColor: colors.surface, + }, + matrixContainer: { + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + padding: SPACING.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + matrixGrid: { + flexDirection: "row", + flexWrap: "wrap", + gap: 6, + }, + matrixCell: { + width: 14, + height: 14, + borderRadius: 3, + }, + matrixLegend: { + flexDirection: "row", + alignItems: "center", + marginTop: 16, + justifyContent: "flex-end", + }, + legendText: { + fontFamily: FONTS.label, + fontSize: 9, + color: colors.outline, + }, + label: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.primary, + letterSpacing: 1.5, + marginBottom: SPACING.xs, + }, + headline: { + fontFamily: FONTS.headline, + fontSize: 28, + color: colors.onSurface, + }, + summaryCard: { + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + padding: SPACING.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + margin: SPACING.lg, + marginTop: 0, + }, + summaryInfo: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: SPACING.md, + }, + summaryTitle: { + fontFamily: FONTS.labelSm, + fontSize: 16, + color: colors.onSurface, + }, + summarySubtitle: { + fontFamily: FONTS.body, + fontSize: 13, + color: colors.outline, + marginTop: 2, + }, + percentageCircle: { + width: 50, + height: 50, + borderRadius: 25, + backgroundColor: colors.primaryContainer, + alignItems: "center", + justifyContent: "center", + }, + percentageText: { + fontFamily: FONTS.labelSm, + fontSize: 14, + color: colors.primary, + }, + progressBarBg: { + height: 6, + backgroundColor: colors.surfaceVariant, + borderRadius: 3, + overflow: "hidden", + }, + progressBarFill: { + height: "100%", + borderRadius: 3, + }, + nudgeCard: { + flexDirection: "row", + alignItems: "center", + padding: 12, + borderRadius: ROUNDNESS.md, + gap: 12, + marginHorizontal: SPACING.lg, + marginBottom: SPACING.lg, + }, + nudgeText: { + flex: 1, + fontFamily: FONTS.body, + fontSize: 13, + lineHeight: 18, + }, + section: { + paddingHorizontal: SPACING.lg, + }, + sectionHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: SPACING.md, + }, + sectionTitle: { + fontFamily: FONTS.headline, + fontSize: 24, + color: colors.onSurface, + }, + addButton: { + flexDirection: "row", + alignItems: "center", + gap: 6, + }, + addButtonText: { + color: colors.primary, + fontFamily: FONTS.labelSm, + fontSize: 11, + letterSpacing: 0.5, + }, + habitList: { + gap: SPACING.md, + }, + habitItemWrapper: { + width: "100%", + }, + habitCard: { + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + padding: 16, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + habitMain: { + flexDirection: "row", + alignItems: "center", + }, + checkBtn: { + width: 32, + height: 32, + borderRadius: 16, + borderWidth: 2, + borderColor: colors.outlineVariant, + alignItems: "center", + justifyContent: "center", + marginRight: 16, + }, + habitInfo: { + flex: 1, + }, + habitTitleRow: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + habitTitle: { + fontFamily: FONTS.labelSm, + fontSize: 17, + color: colors.onSurface, + }, + recoveryTag: { + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 4, + }, + recoveryTagText: { + fontSize: 8, + fontFamily: FONTS.labelSm, + }, + habitMeta: { + flexDirection: "row", + gap: 12, + marginTop: 4, + }, + metaItem: { + flexDirection: "row", + alignItems: "center", + gap: 4, + }, + metaText: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.outline, + }, + settingsBtn: { + padding: 8, + }, + streakBarContainer: { + height: 3, + backgroundColor: colors.surfaceVariant, + borderRadius: 1.5, + marginTop: 12, + overflow: "hidden", + }, + streakBarFill: { + height: "100%", + borderRadius: 1.5, + }, + historyCard: { + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.surfaceVariant + "4D", + padding: 16, + borderRadius: ROUNDNESS.md, + gap: 12, + margin: SPACING.lg, + marginTop: SPACING.xl, + }, + historyText: { + flex: 1, + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurface, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.35)", + justifyContent: "flex-end", + }, + modalContainer: { + backgroundColor: colors.surface, + padding: SPACING.lg, + borderTopLeftRadius: ROUNDNESS.xl, + borderTopRightRadius: ROUNDNESS.xl, + maxHeight: "80%", + }, + modalHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: SPACING.xl, + }, + modalTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + }, + modalBody: { + marginBottom: SPACING.lg, + }, + modalField: { + marginBottom: SPACING.lg, + }, + modalLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1, + marginBottom: 8, + }, + modalInput: { + backgroundColor: colors.surfaceVariant + "4D", + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + padding: 12, + color: colors.onSurface, + fontFamily: FONTS.body, + fontSize: 15, + }, + inputIconWrapper: { + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.surfaceVariant + "4D", + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + inputIcon: { + paddingHorizontal: 12, + }, + adjustmentSection: { + marginTop: SPACING.md, + paddingTop: SPACING.lg, + borderTopWidth: 1, + borderTopColor: colors.outlineVariant + "33", + marginBottom: 20, + }, + backfillBtn: { + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.primary + "10", + padding: 16, + borderRadius: ROUNDNESS.md, + gap: 12, + borderWidth: 1, + borderColor: colors.primary + "33", + }, + backfillText: { + fontFamily: FONTS.labelSm, + fontSize: 14, + color: colors.primary, + }, + modalActions: { + flexDirection: "row", + gap: 12, + marginTop: SPACING.xl, + paddingBottom: Platform.OS === "ios" ? 20 : 0, + }, + modalBtn: { + padding: 14, + borderRadius: ROUNDNESS.md, + alignItems: "center", + justifyContent: "center", + }, + saveBtn: { + flex: 1, + flexDirection: "row", + gap: 8, + }, + saveBtnText: { + fontFamily: FONTS.labelSm, + color: colors.onPrimary, + fontSize: 15, + }, + center: { + justifyContent: "center", + alignItems: "center", + }, + }); diff --git a/src/app/(tabs)/history.tsx b/src/app/(tabs)/history.tsx new file mode 100644 index 0000000..52113b0 --- /dev/null +++ b/src/app/(tabs)/history.tsx @@ -0,0 +1,646 @@ +import AutoHideScrollView from "@/src/components/AutoHideScrollView"; +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useData } from "@/src/hooks/useData"; +import { useTheme } from "@/src/hooks/useTheme"; +import { getLocalDateString } from "@/src/lib/date-utils"; +import { performMutation } from "@/src/lib/sync"; +import * as Haptics from "expo-haptics"; +import { useFocusEffect, useRouter } from "expo-router"; +import { + CheckCircle2, + Clock, + Copy, + History as HistoryIcon, + Layout, + Menu, + Settings, + BookOpen +} from "lucide-react-native"; +import React, { useCallback, useMemo, useState } from "react"; +import { + ActivityIndicator, + Alert, + Image, + StyleSheet, + Text, + TouchableOpacity, + View +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +type TabType = "History" | "Repository" | "Reading"; +type FilterType = "Day" | "Week" | "Month" | "Year"; + +interface Task { + id: string; + title: string; + status: "todo" | "doing" | "done"; + completed_sessions: number; + estimated_sessions: number; + tag: string; + updated_at: string; + todos?: string; +} + +interface ScheduleBlock { + start: string; + end: string; + task: string; + type?: string; + todos?: any[]; + date: string; +} + +interface ReadingLog { + id: string; + book_id: string; + book_title: string; + start_page: number; + end_page: number; + pages_read: number; + duration_seconds: number; + logged_at: string; +} + +export default function HistoryScreen() { + const { colors } = useTheme(); + const { user } = useAuth(); + const router = useRouter(); + const styles = useMemo(() => createStyles(colors), [colors]); + + const [activeTab, setActiveTab] = useState("History"); + const [activeFilter, setActiveFilter] = useState("Month"); + + const userId = user?.id || "guest"; + const today = getLocalDateString(); + + // 1. Fetch Completed Tasks History + const { + data: completedTasks, + loading: tasksLoading, + refresh: refreshTasks, + } = useData( + `SELECT * FROM tasks + WHERE (user_id = ? OR user_id IS NULL) + AND status = 'done' + ORDER BY updated_at DESC LIMIT 200`, + [userId], + ); + + // 2. Fetch Past Schedules + const { + data: pastSchedules, + loading: schedulesLoading, + refresh: refreshSchedules, + } = useData<{ date: string; time_blocks: string }>( + `SELECT date, time_blocks FROM schedules + WHERE (user_id = ? OR user_id IS NULL) + AND date <= ? + ORDER BY date DESC`, + [userId, today], + ); + + // 3. Fetch Reading History + const { + data: readingLogs, + loading: readingLoading, + refresh: refreshReading, + } = useData( + `SELECT rl.*, b.title as book_title + FROM reading_logs rl + JOIN books b ON rl.book_id = b.id + WHERE (rl.user_id = ? OR rl.user_id IS NULL) + ORDER BY rl.logged_at DESC LIMIT 100`, + [userId], + ); + + // Filtering Logic + const filteredHistory = useMemo(() => { + const now = new Date(); + const allHistory = pastSchedules + .flatMap((s) => { + try { + const blocks = JSON.parse(s.time_blocks) as any[]; + return blocks.map((b) => ({ ...b, date: s.date })); + } catch { + return []; + } + }) + .filter((b) => b.type !== "break" && b.task !== "Break"); + + return allHistory.filter((item) => { + const itemDate = new Date(item.date); + const diffTime = now.getTime() - itemDate.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 3600 * 24)); + + if (activeFilter === "Day") return diffDays <= 0; + if (activeFilter === "Week") return diffDays <= 7; + if (activeFilter === "Month") return diffDays <= 31; + if (activeFilter === "Year") return diffDays <= 365; + return true; + }); + }, [pastSchedules, activeFilter]); + + const filteredInventory = useMemo(() => { + const now = new Date(); + return completedTasks.filter((task) => { + const taskDate = new Date(task.updated_at); + const diffTime = now.getTime() - taskDate.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 3600 * 24)); + + if (activeFilter === "Day") return diffDays <= 0; + if (activeFilter === "Week") return diffDays <= 7; + if (activeFilter === "Month") return diffDays <= 31; + if (activeFilter === "Year") return diffDays <= 365; + return true; + }); + }, [completedTasks, activeFilter]); + + const filteredReading = useMemo(() => { + const now = new Date(); + return readingLogs.filter((log) => { + const logDate = new Date(log.logged_at); + const diffTime = now.getTime() - logDate.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 3600 * 24)); + + if (activeFilter === "Day") return diffDays <= 0; + if (activeFilter === "Week") return diffDays <= 7; + if (activeFilter === "Month") return diffDays <= 31; + if (activeFilter === "Year") return diffDays <= 365; + return true; + }); + }, [readingLogs, activeFilter]); + + useFocusEffect( + useCallback(() => { + refreshTasks(); + refreshSchedules(); + refreshReading(); + }, [refreshTasks, refreshSchedules, refreshReading]), + ); + + const formatDuration = (seconds: number) => { + const mins = Math.floor(seconds / 60); + if (mins < 1) return "< 1 min"; + return `${mins} min${mins > 1 ? "s" : ""}`; + }; + + const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + return date.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + }); + }; + + return ( + + + {/* Header */} + + router.push("/menu")} + > + + + + + + router.push("/modal")} + > + + + + + {/* Tab Switcher */} + + { + setActiveTab("History"); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + + Plans + + + { + setActiveTab("Repository"); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + + Tasks + + + { + setActiveTab("Reading"); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + + Reading + + + + + + + {/* Filter Bar */} + + {["Day", "Week", "Month", "Year"].map((f) => ( + setActiveFilter(f as FilterType)} + > + + {f} + + + ))} + + + {activeTab === "History" ? ( + + {schedulesLoading ? ( + + ) : filteredHistory.length > 0 ? ( + filteredHistory.map((block, idx) => ( + handleReuseTask(block)} + > + + + + {block.date === today ? "TODAY" : block.date} + + + + {block.start} - {block.end} + + + + {block.task} + + + REUSE + + + + )) + ) : ( + + + + No historical data for this period. + + + )} + + ) : activeTab === "Reading" ? ( + + {readingLoading ? ( + + ) : filteredReading.length > 0 ? ( + filteredReading.map((log) => ( + + + + {formatDate(log.logged_at)} + + {formatDuration(log.duration_seconds)} + + + + {log.book_title} + + Read {log.pages_read} pages (p. {log.start_page} → {log.end_page}) + + + + + )) + ) : ( + + + No reading history for this period. + + )} + + ) : ( + + {tasksLoading ? ( + + ) : filteredInventory.length > 0 ? ( + filteredInventory.map((task) => ( + handleReuseTask(task)} + > + + + + {task.tag || "General"} + + + {task.title} + + Completed{" "} + {getLocalDateString(new Date(task.updated_at))} + + + + + + + )) + ) : ( + + + + No completed tasks for this period. + + + )} + + )} + + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.background, + height: 60, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + logoImage: { height: 40, width: 160 }, + menuBtn: { padding: 8 }, + ghostBtn: { padding: 8 }, + tabContainer: { + flexDirection: "row", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.surface, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "33", + }, + tab: { + flexDirection: "row", + alignItems: "center", + gap: 8, + paddingVertical: 8, + paddingHorizontal: 16, + marginRight: 12, + borderRadius: ROUNDNESS.full, + }, + activeTab: { + backgroundColor: colors.primaryContainer, + }, + tabText: { + fontFamily: FONTS.labelSm, + fontSize: 14, + color: colors.outline, + }, + scrollContent: { + paddingBottom: 40, + }, + section: { + padding: SPACING.lg, + }, + filterRow: { + flexDirection: "row", + gap: 8, + marginBottom: SPACING.xl, + flexWrap: "wrap", + }, + filterBtn: { + paddingVertical: 6, + paddingHorizontal: 12, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: "transparent", + minWidth: 60, + alignItems: "center", + }, + filterText: { + fontFamily: FONTS.label, + fontSize: 12, + color: colors.onSurfaceVariant, + }, + historyList: { gap: 16 }, + historyCard: { + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + padding: 16, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + cardHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: 12, + }, + dateBadge: { + backgroundColor: colors.surfaceVariant, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + }, + dateText: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.onSurfaceVariant, + }, + timeRange: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + }, + cardBody: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + taskTitle: { + fontFamily: FONTS.headline, + fontSize: 18, + color: colors.onSurface, + flex: 1, + marginRight: 16, + }, + cardActions: { + flexDirection: "row", + alignItems: "center", + gap: 6, + backgroundColor: colors.primary + "1A", + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 6, + }, + reuseText: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + }, + inventoryList: { gap: 12 }, + inventoryCard: { + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.surface, + padding: 16, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + inventoryMain: { flex: 1 }, + tagBadge: { + alignSelf: "flex-start", + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 4, + marginBottom: 6, + }, + tagText: { fontFamily: FONTS.label, fontSize: 9 }, + inventoryTitle: { + fontFamily: FONTS.labelSm, + fontSize: 16, + color: colors.onSurface, + }, + inventoryMeta: { + fontFamily: FONTS.body, + fontSize: 12, + color: colors.outline, + marginTop: 4, + }, + reuseIconBtn: { + padding: 12, + backgroundColor: colors.primaryContainer, + borderRadius: ROUNDNESS.md, + }, + emptyState: { + alignItems: "center", + justifyContent: "center", + paddingVertical: 60, + gap: 16, + }, + emptyText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.outline, + textAlign: "center", + paddingHorizontal: 40, + }, + }); diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx new file mode 100644 index 0000000..b4c0ab3 --- /dev/null +++ b/src/app/(tabs)/index.tsx @@ -0,0 +1,843 @@ +import AutoHideScrollView from "@/src/components/AutoHideScrollView"; +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useData } from "@/src/hooks/useData"; +import { useTheme } from "@/src/hooks/useTheme"; +import { performMutation } from "@/src/lib/sync"; +import * as Haptics from "expo-haptics"; +import { useFocusEffect, useRouter } from "expo-router"; +import { + CheckCircle2, + Globe, + History, + Menu, + Play, + Plus, + Settings, + Sparkles, + Trash2 +} from "lucide-react-native"; +import React, { useCallback, useMemo } from "react"; +import { + ActivityIndicator, + Alert, + Image, + Linking, + StyleSheet, + Text, + TouchableOpacity, + View +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { getLocalDateString } from "@/src/lib/date-utils"; + +export default function DashboardScreen() { + const { colors, focusGoal, displayName } = useTheme(); + const { user } = useAuth(); + const styles = useMemo(() => createStyles(colors), [colors]); + const router = useRouter(); + + const userId = user?.id || "guest"; + const today = getLocalDateString(); + + const { data: habitStats, loading: habitsLoading } = useData<{ + count: number; + }>( + "SELECT COUNT(*) as count FROM habits WHERE is_active = 1 AND (user_id = ? OR user_id IS NULL)", + [userId], + ); + + const { data: logStats, loading: logsLoading } = useData<{ count: number }>( + "SELECT COUNT(DISTINCT l.habit_id) as count FROM logs l JOIN habits h ON l.habit_id = h.id WHERE date(l.logged_at, 'localtime') = date('now', 'localtime') AND (h.user_id = ? OR h.user_id IS NULL)", + [userId], + ); + + const { data: sessionStats } = useData<{ count: number }>( + "SELECT SUM(completed_sessions) as count FROM tasks WHERE (user_id = ? OR user_id IS NULL) AND date(updated_at, 'localtime') = date('now', 'localtime')", + [userId], + ); + + const { + data: habits, + loading: listLoading, + refresh: refreshHabits, + } = useData<{ id: string; title: string; is_done_today: number }>( + `SELECT h.id, h.title, + (SELECT COUNT(*) FROM logs l WHERE l.habit_id = h.id AND date(l.logged_at, 'localtime') = date('now', 'localtime')) as is_done_today + FROM habits h + WHERE h.is_active = 1 AND (h.user_id = ? OR h.user_id IS NULL) LIMIT 3`, + [userId], + ); + + const { + data: shortcuts, + loading: shortcutsLoading, + refresh: refreshShortcuts, + } = useData<{ id: string; title: string; url: string; icon: string }>( + "SELECT * FROM shortcuts WHERE user_id = ? OR user_id IS NULL", + [userId], + ); + + const { data: identitySettings } = useData<{ value: string }>( + "SELECT value FROM settings WHERE key = 'identity_anchor'", + [], + ); + + const { data: latestTask } = useData<{ title: string }>( + "SELECT title FROM tasks WHERE (user_id = ? OR user_id IS NULL) AND status != 'done' ORDER BY updated_at DESC LIMIT 1", + [userId], + ); + + const identityAnchor = + identitySettings?.[0]?.value || "The Disciplined Creator"; + const currentFocus = latestTask?.[0]?.title || "Daily Discipline"; + + // Refresh data when screen is focused + useFocusEffect( + useCallback(() => { + refreshHabits(); + refreshShortcuts(); + }, []), + ); + + const handleToggleHabit = async (habitId: string, isDone: boolean) => { + if (isDone) return; + try { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + await performMutation("logs", "INSERT", { + id: Math.random().toString(36).substring(7), + habit_id: habitId, + status: "completed", + logged_at: new Date().toISOString(), + }); + refreshHabits(); + } catch (err) { + console.error("Failed to log habit:", err); + } + }; + + const activeHabits = habitStats?.[0]?.count || 0; + const completedToday = logStats?.[0]?.count || 0; + const sessionsDone = sessionStats?.[0]?.count || 0; + + const userName = useMemo(() => { + if (displayName) return displayName; + if (user?.email) return user.email.split("@")[0]; + return "Guest"; + }, [displayName, user]); + + const greeting = useMemo(() => { + const hour = new Date().getHours(); + if (hour < 12) return "Good morning"; + if (hour < 17) return "Good afternoon"; + return "Good evening"; + }, []); + + const isLoading = + habitsLoading || logsLoading || listLoading || shortcutsLoading; + + const handleOpenLink = async (url: string) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + try { + const supported = await Linking.canOpenURL(url); + if (supported) { + await Linking.openURL(url); + } else { + Alert.alert("Error", "Don't know how to open this URL: " + url); + } + } catch (error) { + Alert.alert("Error", "An error occurred while trying to open the link"); + } + }; + + const handleDeleteShortcut = async (id: string) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + Alert.alert( + "Delete Shortcut", + "Are you sure you want to remove this shortcut?", + [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + try { + await performMutation("shortcuts", "DELETE", { id }); + refreshShortcuts(); + Haptics.notificationAsync( + Haptics.NotificationFeedbackType.Success, + ); + } catch (error) { + Alert.alert("Error", "Failed to delete shortcut"); + } + }, + }, + ], + ); + }; + + return ( + + + {/* Header */} + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/menu"); + }} + > + + + + + + + + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/history"); + }} + > + + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/modal"); + }} + > + + + + + + + {/* Greeting */} + + DAILY OVERVIEW + + {greeting}, {userName}. Let's find your flow today. + + + + {/* Stats Section */} + + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/hh_habits"); + }} + > + Active Habits + + {isLoading ? "..." : activeHabits} + + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/calendar"); + }} + > + Done Today + + {isLoading ? "..." : completedToday} + + + + + + {/* Current Focus Card */} + + + + + + + + {identityAnchor.toUpperCase()} + + + {currentFocus} + + Refining your presence through focused intention and consistent + action. + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + router.push("/sprint"); + }} + > + Resume Session + + + + + + {/* Focus Capacity Slider */} + + FOCUS CAPACITY + + + + + + + + {sessionsDone} sessions done + + Goal: {focusGoal} + + + + + {/* Keystone Habits */} + + + Daily Habits + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/add-habit"); + }} + > + + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/hh_habits"); + }} + > + View All + + + + + {isLoading ? ( + + ) : habits.length > 0 ? ( + + {habits.map((habit) => ( + 0} + colors={colors} + onToggle={() => + handleToggleHabit(habit.id, habit.is_done_today > 0) + } + /> + ))} + + ) : ( + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/add-habit"); + }} + > + + + Create your first habit to start tracking + + + )} + + + {/* Tools / Shortcuts */} + + + ECOSYSTEM TOOLS + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/add-shortcut"); + }} + > + + + + + {shortcuts.length > 0 ? ( + shortcuts.map((shortcut) => ( + } + label={shortcut.title} + colors={colors} + onPress={() => handleOpenLink(shortcut.url)} + onDelete={() => handleDeleteShortcut(shortcut.id)} + /> + )) + ) : ( + + + No shortcuts added yet. + + + )} + + + + + + ); +} + +function HabitItem({ + title, + done, + colors, + onToggle, +}: { + title: string; + done: boolean; + colors: any; + onToggle: () => void; +}) { + const router = useRouter(); + return ( + + + {done && } + + + {title} + + + ); +} + +const stylesHabit = StyleSheet.create({ + habitItem: { + flexDirection: "row", + alignItems: "center", + gap: 12, + paddingVertical: 8, + }, + checkbox: { + width: 24, + height: 24, + borderRadius: 6, + borderWidth: 2, + alignItems: "center", + justifyContent: "center", + }, + habitText: { + fontFamily: FONTS.body, + fontSize: 16, + }, +}); + +function ToolCard({ + icon, + label, + colors, + onPress, + onDelete, +}: { + icon: React.ReactNode; + label: string; + colors: any; + onPress?: () => void; + onDelete?: () => void; +}) { + return ( + + + Alert.alert( + "External Tool", + `Launching integrated ${label} dashboard.`, + )) + } + > + + {icon} + + + {label} + + + {onDelete && ( + + + + )} + + ); +} + +const stylesTool = StyleSheet.create({ + container: { + width: "47%", + marginBottom: SPACING.md, + position: "relative", + }, + toolCard: { + width: "100%", + padding: SPACING.md, + borderRadius: ROUNDNESS.lg, + alignItems: "center", + justifyContent: "center", + gap: 10, + }, + deleteBtn: { + position: "absolute", + top: 6, + right: 6, + padding: 6, + borderRadius: 12, + }, + iconContainer: { + padding: 10, + borderRadius: ROUNDNESS.md, + }, + toolLabel: { + fontFamily: FONTS.labelSm, + fontSize: 12, + letterSpacing: 0.5, + }, +}); + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + scrollContent: { + paddingBottom: 40, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.background, + height: 60, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + menuBtn: { + padding: 8, + }, + logoImage: { + height: 40, + width: 160, + }, + headerRight: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + ghostBtn: { + padding: 8, + }, + greetingContainer: { + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.lg, + paddingTop: SPACING.md, + }, + labelCaps: { + fontFamily: FONTS.labelSm, + fontSize: 11, + letterSpacing: 1.5, + color: colors.primary, + marginBottom: 4, + }, + greetingText: { + fontFamily: FONTS.headline, + fontSize: 28, + color: colors.onSurface, + lineHeight: 34, + }, + statsContainer: { + paddingHorizontal: SPACING.lg, + marginBottom: SPACING.lg, + }, + statsGrid: { + flexDirection: "row", + gap: SPACING.md, + }, + statCard: { + flex: 1, + backgroundColor: colors.surface, + padding: SPACING.md, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + statLabel: { + fontFamily: FONTS.label, + fontSize: 12, + color: colors.onSurfaceVariant, + marginBottom: 4, + }, + statValue: { + fontSize: 32, + fontFamily: FONTS.headline, + color: colors.onSurface, + }, + focusContainer: { + paddingHorizontal: SPACING.lg, + marginBottom: SPACING.xl, + }, + focusCard: { + backgroundColor: colors.primary, + padding: SPACING.lg, + borderRadius: ROUNDNESS.xl, + }, + focusHeader: { + flexDirection: "row", + alignItems: "center", + gap: 12, + marginBottom: SPACING.md, + }, + focusIconBg: { + padding: 8, + backgroundColor: "rgba(255,255,255,0.2)", + borderRadius: ROUNDNESS.md, + }, + focusBadge: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.onPrimary, + letterSpacing: 1, + }, + focusTitle: { + fontFamily: FONTS.headline, + fontSize: 24, + color: colors.onPrimary, + marginBottom: 8, + }, + focusDesc: { + fontFamily: FONTS.body, + fontSize: 15, + color: colors.onPrimary, + opacity: 0.8, + lineHeight: 22, + marginBottom: SPACING.lg, + }, + primaryBtn: { + backgroundColor: colors.onPrimary, + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: ROUNDNESS.full, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 10, + }, + primaryBtnText: { + color: colors.primary, + fontFamily: FONTS.labelSm, + fontSize: 14, + }, + section: { + padding: SPACING.lg, + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.outline, + letterSpacing: 1.5, + marginBottom: SPACING.md, + }, + sectionTitle: { + fontFamily: FONTS.headline, + fontSize: 24, + color: colors.onSurface, + }, + sectionSubtitle: { + fontFamily: FONTS.label, + fontSize: 13, + color: colors.primary, + }, + rowBetween: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "baseline", + marginBottom: SPACING.md, + }, + rowAlign: { + flexDirection: "row", + alignItems: "center", + }, + sliderContainer: { + backgroundColor: colors.surface, + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + sliderTrack: { + height: 8, + backgroundColor: colors.surfaceVariant, + borderRadius: 4, + position: "relative", + marginBottom: 12, + }, + sliderFill: { + height: "100%", + borderRadius: 4, + }, + sliderThumb: { + width: 20, + height: 20, + borderRadius: 10, + position: "absolute", + top: -6, + marginLeft: -10, + borderWidth: 2, + }, + sliderLabels: { + flexDirection: "row", + justifyContent: "space-between", + }, + sliderLevel: { + fontFamily: FONTS.label, + fontSize: 11, + color: colors.onSurfaceVariant, + }, + habitList: { + gap: 4, + }, + toolsContainer: { + padding: SPACING.lg, + }, + toolsGrid: { + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "space-between", + }, + emptyStateContainer: { + width: "100%", + padding: SPACING.lg, + backgroundColor: colors.surfaceVariant + "4D", + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderStyle: "dashed", + borderColor: colors.outlineVariant, + alignItems: "center", + }, + emptyStateTextSmall: { + fontFamily: FONTS.body, + fontSize: 12, + color: colors.onSurfaceVariant, + }, + emptyStateCard: { + backgroundColor: colors.surface, + padding: SPACING.xl, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderStyle: "dashed", + borderColor: colors.primary, + alignItems: "center", + justifyContent: "center", + gap: 12, + marginTop: 10, + }, + emptyStateText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + textAlign: "center", + }, + }); diff --git a/src/app/(tabs)/library.tsx b/src/app/(tabs)/library.tsx new file mode 100644 index 0000000..1e6b0a7 --- /dev/null +++ b/src/app/(tabs)/library.tsx @@ -0,0 +1,371 @@ +import AutoHideScrollView from "@/src/components/AutoHideScrollView"; +import { SPACING } from "@/src/constants/Theme"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useData } from "@/src/hooks/useData"; +import { useTheme } from "@/src/hooks/useTheme"; +import { supabase } from "@/src/lib/supabase"; +import { performMutation } from "@/src/lib/sync"; +import { getDb } from "@/src/db/database"; +import { decode } from "base-64"; +import * as DocumentPicker from "expo-document-picker"; +import * as FileSystem from "expo-file-system/legacy"; +import { resolveFileUri, getRelativePath, getDocumentDirectory } from "@/src/lib/file-utils"; +import * as Haptics from "expo-haptics"; +import { useFocusEffect, useRouter } from "expo-router"; +import React, { useCallback, useMemo, useState } from "react"; +import { + Alert, + StyleSheet, + View, + TouchableOpacity, +} from "react-native"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { Plus } from "lucide-react-native"; +import { ROUNDNESS } from "@/src/constants/Theme"; + +// Import extracted components +import LibraryHeader from "@/src/components/Library/LibraryHeader"; +import LibraryStats from "@/src/components/Library/LibraryStats"; +import BookGrid from "@/src/components/Library/BookGrid"; +import AddBookModal from "@/src/components/Library/AddBookModal"; +import BookDetailsModal from "@/src/components/Library/BookDetailsModal"; + +interface Book { + id: string; + title: string; + author: string; + total_pages: number; + current_page: number; + file_uri: string; + cover_uri: string; + status: "reading" | "finished" | "want_to_read"; + updated_at: string; + synthesis?: string; +} + +export default function LibraryScreen() { + const { colors, isDark } = useTheme(); + const { user } = useAuth(); + const router = useRouter(); + + const userId = user?.id || "guest"; + + // State + const [showAddModal, setShowAddModal] = useState(false); + const [loading, setLoading] = useState(false); + + // Form State + const [bookTitle, setBookTitle] = useState(""); + const [bookAuthor, setBookAuthor] = useState(""); + const [totalPages, setTotalPages] = useState(""); + const [selectedFile, setSelectedFile] = useState(null); + + // Data + const { + data: books, + loading: booksLoading, + refresh: refreshBooks, + } = useData( + "SELECT * FROM books WHERE (user_id = ? OR user_id IS NULL) ORDER BY updated_at DESC", + [userId], + ); + + const stats = useMemo(() => { + const reading = books.filter((b) => b.status === "reading"); + const finished = books.filter((b) => b.status === "finished"); + const totalPagesRead = books.reduce( + (acc, b) => acc + (b.current_page || 0), + 0, + ); + return { + reading: reading.length, + finished: finished.length, + pages: totalPagesRead, + }; + }, [books]); + + useFocusEffect( + useCallback(() => { + refreshBooks(); + }, [refreshBooks]), + ); + + const handlePickDocument = async () => { + try { + const result = await DocumentPicker.getDocumentAsync({ + type: ["application/pdf", "application/epub+zip"], + copyToCacheDirectory: true, + }); + + if (!result.canceled) { + const file = result.assets[0]; + setSelectedFile(file); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + + try { + const { data, error } = await supabase.functions.invoke( + "process-book-ai", + { + body: { filename: file.name }, + }, + ); + + if (!error && data) { + setBookTitle(data.title || file.name.replace(/\.[^/.]+$/, "")); + setBookAuthor(data.author || ""); + setTotalPages(data.totalPages?.toString() || ""); + } else { + setBookTitle(file.name.replace(/\.[^/.]+$/, "")); + } + } catch (e) { + setBookTitle(file.name.replace(/\.[^/.]+$/, "")); + } finally { + setShowAddModal(true); + } + } + } catch (err) { + Alert.alert("Error", "Failed to pick document"); + } + }; + + const handleAddBook = async () => { + if (!bookTitle.trim()) return; + + setLoading(true); + try { + let finalUri = ""; + if (selectedFile) { + const fileExt = selectedFile.name.split(".").pop(); + const fileName = `${userId}/${Date.now()}.${fileExt}`; + const filePath = `${fileName}`; + + const docDir = getDocumentDirectory(); + const localUri = `${docDir}books/${fileName}`; + const dirPath = `${docDir}books/${userId}`; + await FileSystem.makeDirectoryAsync(dirPath, { intermediates: true }); + await FileSystem.copyAsync({ + from: selectedFile.uri, + to: localUri, + }); + + const base64 = await FileSystem.readAsStringAsync(selectedFile.uri, { + encoding: FileSystem.EncodingType.Base64, + }); + + supabase.storage + .from("books") + .upload(filePath, decode(base64), { + contentType: selectedFile.mimeType || "application/pdf", + upsert: true, + }) + .then(({ error }) => { + if (error) console.error("Cloud sync error:", error); + }); + + finalUri = getRelativePath(localUri); + } + + const id = Math.random().toString(36).substring(7); + await performMutation("books", "INSERT", { + id, + user_id: userId, + title: bookTitle.trim(), + author: bookAuthor.trim() || "Unknown", + total_pages: parseInt(totalPages) || 0, + current_page: 0, + file_uri: finalUri, + status: "reading", + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }); + + setShowAddModal(false); + setBookTitle(""); + setBookAuthor(""); + setTotalPages(""); + setSelectedFile(null); + refreshBooks(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + console.error(e); + Alert.alert("Upload Error", "Failed to add book to library."); + } finally { + setLoading(false); + } + }; + + const [showDetailsModal, setShowDetailsModal] = useState(false); + const [selectedBookForDetails, setSelectedBookForDetails] = + useState(null); + + const openDetails = (book: Book) => { + setSelectedBookForDetails(book); + setShowDetailsModal(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const openReader = (book: Book) => { + setShowDetailsModal(false); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + router.push(`/reader/${book.id}`); + }; + + const handleDeleteBook = async (id: string, fileUri?: string) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + Alert.alert( + "Remove Book", + "Are you sure you want to delete this book from your library?", + [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: async () => { + try { + await performMutation("books", "DELETE", { id }); + + if (fileUri) { + const resolvedUri = resolveFileUri(fileUri); + const fileInfo = await FileSystem.getInfoAsync(resolvedUri); + if (fileInfo.exists) { + await FileSystem.deleteAsync(resolvedUri, { idempotent: true }); + } + } + + const db = await getDb(); + await db.runAsync("DELETE FROM reading_sessions WHERE book_id = ?", [id]); + + refreshBooks(); + Haptics.notificationAsync( + Haptics.NotificationFeedbackType.Success, + ); + } catch (e) { + console.error("Delete error:", e); + Alert.alert("Error", "Failed to delete book"); + } + }, + }, + ], + ); + }; + + const [isGeneratingAI, setIsGeneratingAI] = useState(false); + + const generateAIInsights = async (book: Book) => { + setIsGeneratingAI(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + + try { + const { data, error } = await supabase.functions.invoke( + "process-book-ai", + { + body: { title: book.title }, + }, + ); + + if (error) throw error; + + await performMutation("books", "UPDATE", { + id: book.id, + synthesis: data.synthesis, + updated_at: new Date().toISOString(), + }); + + refreshBooks(); + Alert.alert( + "AI Synthesis Complete", + "Insights have been generated from your reading session.", + ); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + console.error(e); + Alert.alert("AI Error", "Failed to generate insights."); + } finally { + setIsGeneratingAI(false); + } + }; + + return ( + + + + router.push("/modal")} /> + + + + + + + + setShowAddModal(false)} + bookTitle={bookTitle} + onTitleChange={setBookTitle} + bookAuthor={bookAuthor} + onAuthorChange={setBookAuthor} + totalPages={totalPages} + onPagesChange={setTotalPages} + onAddBook={handleAddBook} + loading={loading} + /> + + setShowDetailsModal(false)} + book={selectedBookForDetails as any} + onContinueReading={() => selectedBookForDetails && openReader(selectedBookForDetails)} + onGenerateAI={() => selectedBookForDetails && generateAIInsights(selectedBookForDetails)} + onDelete={() => selectedBookForDetails && handleDeleteBook(selectedBookForDetails.id, selectedBookForDetails.file_uri)} + isGeneratingAI={isGeneratingAI} + /> + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + safeArea: { + flex: 1, + }, + scrollContent: { + paddingBottom: 100, + }, + fab: { + position: "absolute", + right: 20, + bottom: 90, + width: 60, + height: 60, + borderRadius: 30, + justifyContent: "center", + alignItems: "center", + elevation: 8, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 4, + zIndex: 10, + }, +}); diff --git a/src/app/(tabs)/sprint.tsx b/src/app/(tabs)/sprint.tsx new file mode 100644 index 0000000..5aa4b74 --- /dev/null +++ b/src/app/(tabs)/sprint.tsx @@ -0,0 +1,1408 @@ +import AutoHideScrollView from "@/src/components/AutoHideScrollView"; +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useData } from "@/src/hooks/useData"; +import { useTheme } from "@/src/hooks/useTheme"; +import { performMutation } from "@/src/lib/sync"; +import * as Haptics from "expo-haptics"; +import { useFocusEffect, useRouter } from "expo-router"; +import { + Check, + ListTodo, + Menu, + Pause, + Play, + Plus, + RotateCcw, + Settings, + X, + Zap +} from "lucide-react-native"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + ActivityIndicator, + Alert, + AppState, + Image, + KeyboardAvoidingView, + Modal, + Platform, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { getLocalDateString } from "@/src/lib/date-utils"; + +interface Task { + id: string; + title: string; + status: "todo" | "doing" | "done"; + estimated_sessions: number; + completed_sessions: number; + tag: string; + todos?: string; // JSON string +} + +interface Schedule { + id: string; + time_blocks: string; +} + +interface TimeBlock { + start: string; + end: string; + task: string; + type?: string; + todos?: { id: string; text: string; completed: boolean }[]; +} + +export default function SprintScreen() { + const { colors, focusGoal, sprintDuration } = useTheme(); + const { user } = useAuth(); + const styles = useMemo(() => createStyles(colors), [colors]); + const router = useRouter(); + + const userId = user?.id || "guest"; + const today = getLocalDateString(); + + // Timer State + const [manualBlock, setManualBlock] = useState(null); + const [isActive, setIsActive] = useState(false); + const [timerMode, setTimerMode] = useState<"focus" | "break">("focus"); + const [targetEndTime, setTargetEndTime] = useState(null); + const [seconds, setSeconds] = useState(sprintDuration * 60); + + // Modal State + const [showTodoModal, setShowTodoModal] = useState(false); + const [selectedTaskForTodo, setSelectedTaskForTodo] = useState( + null, + ); + const [newTodoText, setNewTodoText] = useState(""); + + const appState = useRef(AppState.currentState); + + const { + data: tasks, + loading, + refresh: refreshTasks, + } = useData( + "SELECT * FROM tasks WHERE (user_id = ? OR user_id IS NULL) AND status != 'done' ORDER BY created_at DESC", + [userId], + ); + + const { data: schedules, refresh: refreshSchedule } = useData( + "SELECT * FROM schedules WHERE date = ? AND (user_id = ? OR user_id IS NULL)", + [today, userId], + ); + + const { data: completedToday, refresh: refreshLogs } = useData<{ + count: number; + }>( + "SELECT COUNT(*) as count FROM logs WHERE date(logged_at, 'localtime') = date('now', 'localtime') AND status = 'completed'", + [], + ); + + const allBlocks = useMemo(() => { + if (!schedules.length) return []; + try { + return JSON.parse(schedules[0].time_blocks) as TimeBlock[]; + } catch { + return []; + } + }, [schedules]); + + const autoActiveBlock = useMemo(() => { + const now = new Date(); + const currentMinutes = now.getHours() * 60 + now.getMinutes(); + + return allBlocks.find((b) => { + const [startH, startM] = b.start.split(":").map(Number); + const [endH, endM] = b.end.split(":").map(Number); + const start = startH * 60 + startM; + const end = endH * 60 + endM; + return currentMinutes >= start && currentMinutes <= end; + }); + }, [allBlocks]); + + const activeTimeBlock = manualBlock || autoActiveBlock; + + const activeTask = useMemo(() => { + return tasks.find((t) => t.status === "doing") || tasks[0]; + }, [tasks]); + + const refresh = useCallback(() => { + refreshTasks(); + refreshSchedule(); + refreshLogs(); + }, [refreshTasks, refreshSchedule, refreshLogs]); + + useFocusEffect( + useCallback(() => { + refresh(); + }, [refresh]), + ); + + // Background timer sync + useEffect(() => { + const subscription = AppState.addEventListener("change", (nextAppState) => { + if ( + appState.current.match(/inactive|background/) && + nextAppState === "active" + ) { + if (isActive && targetEndTime) { + const now = Date.now(); + const remaining = Math.max( + 0, + Math.ceil((targetEndTime - now) / 1000), + ); + setSeconds(remaining); + if (remaining === 0) { + handleSessionComplete(); + } + } + } + appState.current = nextAppState; + }); + + return () => { + subscription.remove(); + }; + }, [isActive, targetEndTime]); + + const handleToggleTimer = () => { + if (!isActive) { + // Starting/Resuming + const duration = timerMode === "focus" ? sprintDuration * 60 : 5 * 60; + const newTarget = Date.now() + (seconds > 0 ? seconds : duration) * 1000; + setTargetEndTime(newTarget); + setIsActive(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + } else { + // Pausing + setIsActive(false); + setTargetEndTime(null); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + }; + + const handleToggleTodo = async (todoId: string) => { + if (!activeTimeBlock || !schedules[0]) return; + + try { + const updatedBlocks = allBlocks.map((b) => { + if ( + b.start === activeTimeBlock.start && + b.task === activeTimeBlock.task + ) { + const updatedTodos = b.todos?.map((t) => + t.id === todoId ? { ...t, completed: !t.completed } : t, + ); + const updatedBlock = { ...b, todos: updatedTodos }; + if ( + manualBlock && + manualBlock.start === b.start && + manualBlock.task === b.task + ) { + setManualBlock(updatedBlock); + } + return updatedBlock; + } + return b; + }); + + await performMutation("schedules", "UPDATE", { + id: schedules[0].id, + time_blocks: JSON.stringify(updatedBlocks), + }); + refreshSchedule(); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } catch (e) { + console.error("Failed to toggle block todo", e); + } + }; + + // Only initialize timer once or when sprintDuration changes while inactive + useEffect(() => { + if (!isActive && timerMode === "focus") { + setSeconds(sprintDuration * 60); + } + }, [sprintDuration, timerMode]); + + useEffect(() => { + let interval: any = null; + if (isActive && seconds > 0) { + interval = setInterval(() => { + setSeconds((prev) => { + if (prev <= 1) { + handleSessionComplete(); + return 0; + } + return prev - 1; + }); + }, 1000); + } else { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [isActive, seconds]); + + const handleSessionComplete = async () => { + setIsActive(false); + setTargetEndTime(null); + + if (timerMode === "focus") { + try { + await performMutation("logs", "INSERT", { + id: Math.random().toString(36).substring(7), + habit_id: "focus-session", + status: "completed", + logged_at: new Date().toISOString(), + }); + + if (activeTask) { + await performMutation("tasks", "UPDATE", { + id: activeTask.id, + completed_sessions: (activeTask.completed_sessions || 0) + 1, + updated_at: new Date().toISOString(), + }); + } + + refresh(); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + + // Transition to Break + setTimerMode("break"); + const breakSecs = 5 * 60; + setSeconds(breakSecs); + setIsActive(true); + setTargetEndTime(Date.now() + breakSecs * 1000); + + Alert.alert( + "Session Complete", + "Focus session logged! Starting break.", + ); + } catch (e) { + console.error("Failed to complete session", e); + } + } else { + // Break complete -> Shift to next task + setTimerMode("focus"); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + + // If we have a next block coming up, we can "jump" to it or just reset to default focus duration + setSeconds(sprintDuration * 60); + + // If the manual block was finished, clear it to let auto-detection find the next one + if (manualBlock) { + setManualBlock(null); + } + + Alert.alert("Break Over", "Time to dive into the next task."); + } + }; + + const handleExtendSession = async (mins: number = 10) => { + const additionalSecs = mins * 60; + setSeconds((prev) => prev + additionalSecs); + if (isActive && targetEndTime) { + setTargetEndTime(targetEndTime + additionalSecs * 1000); + } + + // If we're extending a scheduled block, we should try to update the schedule end time too + if (activeTimeBlock && schedules[0]) { + try { + const [h, m] = activeTimeBlock.end.split(":").map(Number); + const newEndMins = h * 60 + m + mins; + const newEndStr = `${String(Math.floor(newEndMins / 60)).padStart(2, "0")}:${String(newEndMins % 60).padStart(2, "0")}`; + + const updatedBlocks = allBlocks.map((b) => { + if ( + b.start === activeTimeBlock.start && + b.task === activeTimeBlock.task + ) { + const updated = { ...b, end: newEndStr }; + if (manualBlock) setManualBlock(updated); + return updated; + } + return b; + }); + + await performMutation("schedules", "UPDATE", { + id: schedules[0].id, + time_blocks: JSON.stringify(updatedBlocks), + }); + refreshSchedule(); + } catch (e) { + console.error("Failed to extend schedule block", e); + } + } + + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); + }; + + const handleTaskTodoToggle = async (taskId: string, todoId: string) => { + const task = tasks.find((t) => t.id === taskId); + if (!task) return; + + try { + const currentTodos = task.todos ? JSON.parse(task.todos) : []; + const updatedTodos = currentTodos.map((t: any) => + t.id === todoId ? { ...t, completed: !t.completed } : t, + ); + + await performMutation("tasks", "UPDATE", { + id: taskId, + todos: JSON.stringify(updatedTodos), + updated_at: new Date().toISOString(), + }); + refreshTasks(); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } catch (e) { + console.error("Failed to toggle task todo", e); + } + }; + + const handleAddTaskTodo = async () => { + if (!selectedTaskForTodo || !newTodoText.trim()) return; + + try { + const currentTodos = selectedTaskForTodo.todos + ? JSON.parse(selectedTaskForTodo.todos) + : []; + const newTodo = { + id: Math.random().toString(36).substring(7), + text: newTodoText.trim(), + completed: false, + }; + + const updatedTodos = [...currentTodos, newTodo]; + + await performMutation("tasks", "UPDATE", { + id: selectedTaskForTodo.id, + todos: JSON.stringify(updatedTodos), + updated_at: new Date().toISOString(), + }); + + setNewTodoText(""); + refreshTasks(); + // Update local state to show change in modal immediately + setSelectedTaskForTodo({ + ...selectedTaskForTodo, + todos: JSON.stringify(updatedTodos), + }); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } catch (e) { + console.error("Failed to add task todo", e); + } + }; + + const formatTime = (secs: number) => { + const mins = Math.floor(secs / 60); + const s = secs % 60; + return `${mins.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; + }; + + const handleReset = () => { + setIsActive(false); + setSeconds(sprintDuration * 60); + setManualBlock(null); + }; + + const handleToggleTaskStatus = async (task: Task) => { + let newStatus: "todo" | "doing" | "done" = "todo"; + if (task.status === "todo") newStatus = "doing"; + else if (task.status === "doing") newStatus = "done"; + + try { + await performMutation("tasks", "UPDATE", { + id: task.id, + status: newStatus, + updated_at: new Date().toISOString(), + }); + refresh(); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + } catch (e) { + console.error("Failed to update task status", e); + } + }; + + const sessionsDone = completedToday?.[0]?.count || 0; + const sessionGoal = focusGoal; + + if (loading && tasks.length === 0) { + return ( + + + + ); + } + + return ( + + + {/* Header */} + + router.push("/menu")} + > + + + + + + + + router.push("/modal")} + > + + + + + + {/* Timer Section */} + + FOCUSED SPRINT + + {formatTime(seconds)} + + + + + + + + + {isActive ? ( + + ) : ( + + )} + + + handleExtendSession(10)} + disabled={timerMode === "break"} + > + + + + {timerMode === "focus" && ( + +10 MIN + )} + + + {/* Current Schedule Focus */} + {activeTimeBlock && ( + + + + + + + {manualBlock ? "MANUAL FOCUS" : "CURRENT SCHEDULE"} + + + + {activeTimeBlock.start} - {activeTimeBlock.end} + + + + + + {activeTimeBlock.task} + + { + const task = tasks.find( + (t) => t.title === activeTimeBlock.task, + ); + if (task) { + setSelectedTaskForTodo(task); + setShowTodoModal(true); + } else { + Alert.alert( + "No Task Found", + "This schedule block isn't linked to a task inventory item yet.", + ); + } + }} + > + + + SUB-TASKS + + + + + {activeTimeBlock.todos && activeTimeBlock.todos.length > 0 && ( + + {activeTimeBlock.todos.map((todo) => ( + handleToggleTodo(todo.id)} + > + + {todo.completed && ( + + )} + + + {todo.text} + + + ))} + + )} + {manualBlock && ( + setManualBlock(null)} + > + + Restore Automatic Schedule + + + )} + + + )} + + {/* Schedule Browser */} + {allBlocks.length > 0 && ( + + TODAY'S SCHEDULE + + {allBlocks.map((block, idx) => { + const isCurrent = + activeTimeBlock?.start === block.start && + activeTimeBlock?.task === block.task; + return ( + { + setManualBlock(block); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + {block.start} + + + {block.task} + + {isCurrent && ( + + + + )} + + ); + })} + + + )} + + {/* Session Progress */} + + + + DAILY FOCUS GOAL + + {sessionsDone} / {sessionGoal} + + + + + + + {sessionsDone >= sessionGoal + ? "Daily goal achieved! You're in peak flow." + : `Complete ${sessionGoal - sessionsDone} more sessions to reach your daily target.`} + + + + + {/* Task Queue */} + + + Task Inventory + router.push("/add-task")} + > + + ADD TASK + + + + + {tasks.length > 0 ? ( + tasks.map((task) => ( + handleToggleTaskStatus(task)} + > + + {task.status === "doing" && ( + + )} + + + + {task.title} + + + + {task.tag} + + + {task.completed_sessions}/{task.estimated_sessions}{" "} + sessions + + + + {task.status === "doing" && ( + + ACTIVE + + )} + + )) + ) : ( + router.push("/add-task")} + > + + + No tasks in your queue. Add something to focus on. + + + )} + + + + + {/* Task Todo Modal */} + + + + + + Sub-tasks + + {selectedTaskForTodo?.title} + + + setShowTodoModal(false)}> + + + + + + + + + + + + + {selectedTaskForTodo?.todos ? ( + JSON.parse(selectedTaskForTodo.todos).map((todo: any) => ( + + + handleTaskTodoToggle(selectedTaskForTodo.id, todo.id) + } + style={[ + styles.modalTodoCheck, + { borderColor: colors.primary }, + todo.completed && { backgroundColor: colors.primary }, + ]} + > + {todo.completed && ( + + )} + + + {todo.text} + + + )) + ) : ( + + + + Break down this task into smaller steps. + + + )} + + + + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + scrollContent: { + paddingBottom: 40, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.background, + height: 60, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + menuBtn: { + padding: 8, + }, + logoImage: { + height: 40, + width: 160, + }, + ghostBtn: { + padding: 8, + }, + timerSection: { + padding: SPACING.xl, + alignItems: "center", + backgroundColor: colors.surface, + }, + labelCaps: { + fontFamily: FONTS.labelSm, + fontSize: 12, + letterSpacing: 1.5, + color: colors.primary, + marginBottom: SPACING.xl, + }, + timerDisplayContainer: { + width: 260, + height: 260, + borderRadius: 130, + borderWidth: 8, + borderColor: colors.primary + "1A", + alignItems: "center", + justifyContent: "center", + }, + timerDisplay: { + fontSize: 72, + fontFamily: FONTS.labelSm, + color: colors.onSurface, + letterSpacing: -1, + }, + timerControls: { + flexDirection: "row", + alignItems: "center", + gap: SPACING.xxl, + marginTop: SPACING.xl, + }, + extendLabel: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.primary, + marginTop: 8, + letterSpacing: 1, + }, + controlBtn: { + padding: 12, + borderRadius: ROUNDNESS.full, + backgroundColor: colors.surfaceVariant, + }, + controlBtnPlaceholder: { + width: 48, + }, + playBtn: { + width: 84, + height: 84, + borderRadius: 42, + alignItems: "center", + justifyContent: "center", + elevation: 4, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + }, + section: { + padding: SPACING.lg, + }, + progressCard: { + backgroundColor: colors.surface, + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + rowBetween: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "baseline", + marginBottom: SPACING.md, + }, + dataLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + letterSpacing: 1, + color: colors.onSurfaceVariant, + }, + dataValueSmall: { + fontFamily: FONTS.headline, + fontSize: 18, + color: colors.primary, + }, + technicalBarBg: { + height: 8, + backgroundColor: colors.surfaceVariant, + borderRadius: 4, + overflow: "hidden", + }, + technicalBarFill: { + height: "100%", + borderRadius: 4, + }, + progressHint: { + marginTop: SPACING.md, + fontFamily: FONTS.body, + fontSize: 13, + color: colors.onSurfaceVariant, + }, + sectionTitle: { + fontFamily: FONTS.headline, + fontSize: 24, + color: colors.onSurface, + }, + addBtn: { + flexDirection: "row", + alignItems: "center", + gap: 6, + }, + addBtnText: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 0.5, + }, + taskQueue: { + gap: 12, + marginTop: 16, + }, + taskItem: { + flexDirection: "row", + alignItems: "center", + padding: SPACING.md, + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + checkbox: { + width: 24, + height: 24, + borderRadius: 6, + borderWidth: 2, + alignItems: "center", + justifyContent: "center", + }, + taskInfo: { + marginLeft: 12, + flex: 1, + }, + taskTitle: { + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + }, + taskMeta: { + flexDirection: "row", + alignItems: "center", + gap: 12, + marginTop: 4, + }, + tag: { + alignSelf: "flex-start", + backgroundColor: colors.secondaryContainer, + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 4, + }, + tagText: { + fontFamily: FONTS.labelSm, + fontSize: 10, + color: colors.onSecondaryContainer, + }, + sessionCount: { + fontFamily: FONTS.label, + fontSize: 11, + color: colors.onSurfaceVariant, + }, + activeBadge: { + backgroundColor: colors.primary, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + }, + activeBadgeText: { + color: colors.onPrimary, + fontFamily: FONTS.labelSm, + fontSize: 9, + }, + emptyStateCard: { + backgroundColor: colors.surface, + padding: SPACING.xl, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderStyle: "dashed", + borderColor: colors.primary, + alignItems: "center", + justifyContent: "center", + gap: 12, + }, + emptyStateText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + textAlign: "center", + }, + activeBlockCard: { + backgroundColor: colors.surface, + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.primary, + backgroundColor: colors.primary + "0D", + }, + activeBlockLabel: { + fontFamily: FONTS.labelSm, + fontSize: 10, + color: colors.primary, + letterSpacing: 1, + marginLeft: 8, + }, + activeBlockTime: { + fontFamily: FONTS.label, + fontSize: 11, + color: colors.onSurfaceVariant, + }, + activeBlockTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + marginTop: 8, + marginBottom: SPACING.md, + flex: 1, + }, + subtaskBtn: { + flexDirection: "row", + alignItems: "center", + gap: 6, + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 6, + }, + subtaskBtnText: { + fontFamily: FONTS.labelSm, + fontSize: 10, + letterSpacing: 0.5, + }, + activeTodoList: { + gap: 8, + borderTopWidth: 1, + borderTopColor: colors.outlineVariant + "33", + paddingTop: SPACING.md, + }, + activeTodoItem: { + flexDirection: "row", + alignItems: "center", + gap: 12, + paddingVertical: 4, + }, + todoCheck: { + width: 20, + height: 20, + borderRadius: 5, + borderWidth: 2, + alignItems: "center", + justifyContent: "center", + }, + activeTodoText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurface, + }, + rowAlign: { + flexDirection: "row", + alignItems: "center", + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.outline, + letterSpacing: 1.5, + marginBottom: SPACING.md, + }, + scheduleRow: { + gap: 12, + paddingRight: SPACING.lg, + }, + scheduleBlockCard: { + width: 120, + padding: SPACING.md, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + blockTimeText: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.primary, + marginBottom: 4, + }, + blockTaskText: { + fontFamily: FONTS.labelSm, + fontSize: 13, + color: colors.onSurface, + }, + clearManualBtn: { + marginTop: SPACING.md, + paddingTop: SPACING.md, + borderTopWidth: 1, + borderTopColor: colors.outlineVariant + "33", + alignItems: "center", + }, + clearManualText: { + fontFamily: FONTS.label, + fontSize: 11, + textDecorationLine: "underline", + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.5)", + justifyContent: "flex-end", + }, + modalContainer: { + backgroundColor: colors.surface, + borderTopLeftRadius: ROUNDNESS.xl, + borderTopRightRadius: ROUNDNESS.xl, + padding: SPACING.lg, + maxHeight: "80%", + }, + modalHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-start", + marginBottom: SPACING.lg, + }, + modalTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + }, + modalSubtitle: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + marginTop: 2, + }, + todoInputRow: { + flexDirection: "row", + gap: 10, + marginBottom: SPACING.lg, + }, + modalInput: { + flex: 1, + height: 48, + backgroundColor: colors.surfaceVariant + "4D", + borderRadius: ROUNDNESS.md, + paddingHorizontal: 16, + fontFamily: FONTS.body, + fontSize: 15, + color: colors.onSurface, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + addTodoBtn: { + width: 48, + height: 48, + borderRadius: ROUNDNESS.md, + alignItems: "center", + justifyContent: "center", + }, + modalTodoList: { + marginBottom: SPACING.xl, + }, + modalTodoItem: { + flexDirection: "row", + alignItems: "center", + gap: 12, + paddingVertical: 12, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "1A", + }, + modalTodoCheck: { + width: 22, + height: 22, + borderRadius: 6, + borderWidth: 2, + alignItems: "center", + justifyContent: "center", + }, + modalTodoText: { + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + flex: 1, + }, + emptyTodos: { + alignItems: "center", + justifyContent: "center", + paddingVertical: 40, + gap: 12, + }, + emptyTodosText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.outline, + textAlign: "center", + }, + }); diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx new file mode 100644 index 0000000..d3d5236 --- /dev/null +++ b/src/app/_layout.tsx @@ -0,0 +1,139 @@ +import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; +import { Stack } from 'expo-router'; +import { StatusBar } from 'expo-status-bar'; +import { useEffect, useState, useRef } from 'react'; +import * as SplashScreen from 'expo-splash-screen'; +import { useFonts } from 'expo-font'; +import { Manrope_700Bold, Manrope_400Regular } from '@expo-google-fonts/manrope'; +import { PlusJakartaSans_500Medium, PlusJakartaSans_700Bold } from '@expo-google-fonts/plus-jakarta-sans'; +import { initDatabase } from '@/src/db/database'; +import { useTheme, BatsirThemeProvider } from '@/src/hooks/useTheme'; +import { useSync } from '@/src/hooks/useSync'; +import { AnimatedSplashScreen } from '@/src/components/animated-splash-screen'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { configureReanimatedLogger, ReanimatedLogLevel } from 'react-native-reanimated'; + +// Disable strict mode for Reanimated logger to suppress warnings about reading value during render, +// which can be triggered by the React Compiler or third-party libraries in Reanimated 4. +configureReanimatedLogger({ + level: ReanimatedLogLevel.warn, + strict: false, +}); + +export { + // Catch any errors thrown by the Layout component. + ErrorBoundary, +} from 'expo-router'; + +export const unstable_settings = { + // Ensure that reloading on `/modal` keeps a back button present. + initialRouteName: '(tabs)', +}; + +// Prevent the splash screen from auto-hiding before asset loading is complete. +SplashScreen.preventAutoHideAsync().catch(() => { + /* Reloading in development can sometimes cause this to fail, ignore it */ +}); + +export default function RootLayout() { + const [dbLoaded, setDbLoaded] = useState(false); + const [fontsLoaded, fontError] = useFonts({ + Manrope_700Bold, + Manrope_400Regular, + PlusJakartaSans_500Medium, + PlusJakartaSans_700Bold, + }); + + useEffect(() => { + initDatabase() + .then(() => setDbLoaded(true)) + .catch((err) => console.error('Database initialization failed:', err)); + }, []); + + useEffect(() => { + if (fontError) throw fontError; + }, [fontError]); + + if (!fontsLoaded || !dbLoaded) { + return null; + } + + return ( + + + + + + + ); +} + +function SyncWrapper() { + useSync(); + return null; +} + +function RootLayoutContent() { + const { isLoaded, colors } = useTheme(); + const [animationFinished, setAnimationFinished] = useState(false); + const splashHidden = useRef(false); + + useEffect(() => { + if (isLoaded && !splashHidden.current) { + const hideSplash = async () => { + try { + splashHidden.current = true; + // Verify if we can actually hide it + await SplashScreen.hideAsync(); + } catch (e) { + // If it fails, it usually means it's already hidden or not registered + console.log("Splash hide safely ignored:", e); + } + }; + + const timer = setTimeout(hideSplash, 200); + return () => clearTimeout(timer); + } + }, [isLoaded]); + + if (!isLoaded) { + return null; + } + + return ( + <> + + {!animationFinished && ( + setAnimationFinished(true)} + backgroundColor={colors.background} + /> + )} + + ); +} + +function RootLayoutNav() { + const { colorScheme } = useTheme(); + + return ( + + + + + + + + + + + {/* Secondary Screens now in root stack */} + + + + + + + + ); +} diff --git a/src/app/add-habit.tsx b/src/app/add-habit.tsx new file mode 100644 index 0000000..8e2cb90 --- /dev/null +++ b/src/app/add-habit.tsx @@ -0,0 +1,571 @@ +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useTheme } from "@/src/hooks/useTheme"; +import { useData } from "@/src/hooks/useData"; +import { performMutation } from "@/src/lib/sync"; +import * as Haptics from "expo-haptics"; +import { useRouter } from "expo-router"; +import { + Calendar, + Clock, + Repeat, + Save, + Sparkles, + X, + MapPin, + Anchor, + ChevronDown, +} from "lucide-react-native"; +import React, { useMemo, useState } from "react"; +import { + ActivityIndicator, + Alert, + ScrollView, + StyleSheet, + Switch, + Text, + TextInput, + TouchableOpacity, + View, + Modal, + KeyboardAvoidingView, + Platform +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { TimeInput } from "@/src/components/ui/TimeInput"; + +const FREQUENCIES = [ + { label: "Daily", value: "daily" }, + { label: "Weekly", value: "weekly" }, + { label: "Monthly", value: "monthly" }, +]; + +const TIME_PRESETS = [ + { label: "Morning", time: "08:00" }, + { label: "Noon", time: "12:00" }, + { label: "Evening", time: "18:00" }, + { label: "Night", time: "22:00" }, +]; + +export default function AddHabitScreen() { + const { colors } = useTheme(); + const { user } = useAuth(); + const styles = useMemo(() => createStyles(colors), [colors]); + const router = useRouter(); + + const userId = user?.id || 'guest'; + const { data: existingHabits } = useData<{id: string, title: string}>( + 'SELECT id, title FROM habits WHERE is_active = 1 AND (user_id = ? OR user_id IS NULL)', + [userId] + ); + + const [title, setTitle] = useState(""); + const [frequency, setFrequency] = useState("daily"); + const [preferredTime, setPreferredTime] = useState("08:00"); + const [location, setLocation] = useState(""); + const [twoMinuteVersion, setTwoMinuteVersion] = useState(""); + const [anchorHabitId, setAnchorHabitId] = useState(null); + const [weekendFlexibility, setWeekendFlexibility] = useState(false); + const [loading, setLoading] = useState(false); + const [showAnchorModal, setShowAnchorModal] = useState(false); + + const selectedAnchor = useMemo(() => + existingHabits.find(h => h.id === anchorHabitId), + [existingHabits, anchorHabitId] + ); + + const handleSave = async () => { + if (!title) { + Alert.alert("Error", "Please provide a title for your habit"); + return; + } + + setLoading(true); + try { + await performMutation("habits", "INSERT", { + id: Math.random().toString(36).substring(7), + user_id: userId, + title, + frequency, + preferred_time: preferredTime, + location: location, + two_minute_version: twoMinuteVersion, + anchor_habit_id: anchorHabitId, + weekend_flexibility: weekendFlexibility ? 1 : 0, + is_active: 1, + }); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + router.back(); + } catch (error) { + console.error("Failed to save habit:", error); + Alert.alert("Error", "Failed to save habit"); + } finally { + setLoading(false); + } + }; + + return ( + + + + + router.back()}> + + + New Habit + + {loading ? ( + + ) : ( + + )} + + + + + + HABIT ARCHITECT + + {/* Title */} + + WHAT IS THE HABIT? + + + + + + + {/* Implementation Intentions: Location */} + + WHERE WILL YOU DO IT? (LOCATION) + + + + + + + {/* Small Start: Two-Minute Version */} + + THE TWO-MINUTE VERSION (START SMALL) + + + + + "Optimize for the starting line, not the finish line." + + + {/* Habit Stacking: Anchor Habit */} + + STACK IT: AFTER I... + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + setShowAnchorModal(true); + }} + > + + + {selectedAnchor ? selectedAnchor.title : "Choose an anchor habit"} + + + + + + {/* Time */} + + + + {TIME_PRESETS.map((p) => ( + { + setPreferredTime(p.time); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + {p.label} + + + ))} + + + + {/* Frequency */} + + FREQUENCY + + {FREQUENCIES.map((f) => ( + setFrequency(f.value)} + > + + + {f.label} + + + ))} + + + + {/* Weekend Flexibility */} + + + + + Weekend Flexibility + + + Allow skipping on weekends without breaking streaks. + + + + + + + + + "Every action you take is a vote for the type of person you wish to become." + + + + + + + {/* Anchor Habit Modal */} + setShowAnchorModal(false)} + > + + + + Select Anchor Habit + setShowAnchorModal(false)}> + + + + + { + setAnchorHabitId(null); + setShowAnchorModal(false); + }} + > + + No Anchor (Independent) + + + {existingHabits.map((habit) => ( + { + setAnchorHabitId(habit.id); + setShowAnchorModal(false); + }} + > + + {habit.title} + + + ))} + + + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: SPACING.lg, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "4D", + }, + headerTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + }, + content: { + padding: SPACING.lg, + }, + section: { + gap: SPACING.xl, + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1.5, + marginBottom: 4, + }, + inputGroup: { + gap: 8, + }, + label: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.outline, + }, + inputWrapper: { + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + paddingHorizontal: 12, + }, + inputIcon: { + marginRight: 10, + }, + input: { + flex: 1, + height: 52, + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + }, + hintText: { + fontFamily: FONTS.body, + fontSize: 12, + color: colors.onSurfaceVariant, + fontStyle: 'italic', + marginTop: 2, + }, + presetsGrid: { + flexDirection: "row", + gap: 8, + marginTop: 4, + }, + presetBtn: { + flex: 1, + paddingVertical: 8, + borderRadius: ROUNDNESS.sm, + backgroundColor: colors.surfaceVariant + "4D", + alignItems: "center", + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + presetText: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.onSurfaceVariant, + }, + frequencyGrid: { + flexDirection: "row", + gap: 10, + }, + frequencyOption: { + flex: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + backgroundColor: colors.surface, + paddingVertical: 12, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + frequencyText: { + fontFamily: FONTS.label, + fontSize: 13, + color: colors.onSurfaceVariant, + }, + switchRow: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + backgroundColor: colors.surface, + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + switchContent: { + flex: 1, + paddingRight: 16, + }, + rowAlign: { + flexDirection: "row", + alignItems: "center", + marginBottom: 4, + }, + switchTitle: { + fontFamily: FONTS.headline, + fontSize: 16, + color: colors.onSurface, + }, + switchDesc: { + fontFamily: FONTS.body, + fontSize: 13, + color: colors.onSurfaceVariant, + lineHeight: 18, + }, + infoCard: { + backgroundColor: colors.primaryContainer + "40", + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.primaryContainer, + marginTop: SPACING.xxl, + alignItems: "center", + }, + infoText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + fontStyle: "italic", + textAlign: "center", + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'flex-end', + }, + modalContent: { + borderTopLeftRadius: ROUNDNESS.xl, + borderTopRightRadius: ROUNDNESS.xl, + paddingBottom: 40, + maxHeight: '70%', + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: SPACING.lg, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + '4D', + }, + modalTitle: { + fontFamily: FONTS.headline, + fontSize: 18, + color: colors.onSurface, + }, + anchorList: { + padding: SPACING.md, + }, + anchorItem: { + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + marginBottom: 8, + }, + anchorItemText: { + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + }, + }); diff --git a/src/app/add-shortcut.tsx b/src/app/add-shortcut.tsx new file mode 100644 index 0000000..bc347a7 --- /dev/null +++ b/src/app/add-shortcut.tsx @@ -0,0 +1,192 @@ +import React, { useState, useMemo } from 'react'; +import { StyleSheet, View, Text, TextInput, TouchableOpacity, ScrollView, Alert, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { X, Save, Link as LinkIcon, Type, Sparkles } from 'lucide-react-native'; +import { SPACING, FONTS, ROUNDNESS } from '@/src/constants/Theme'; +import { useTheme } from '@/src/hooks/useTheme'; +import { performMutation } from '@/src/lib/sync'; +import { useRouter } from 'expo-router'; +import { useAuth } from '@/src/hooks/useAuth'; + +export default function AddShortcutScreen() { + const { colors } = useTheme(); + const { user } = useAuth(); + const styles = useMemo(() => createStyles(colors), [colors]); + const router = useRouter(); + + const [title, setTitle] = useState(''); + const [url, setUrl] = useState(''); + const [loading, setLoading] = useState(false); + + const handleSave = async () => { + if (!title || !url) { + Alert.alert('Error', 'Please provide both a title and a URL'); + return; + } + + setLoading(true); + try { + await performMutation('shortcuts', 'INSERT', { + id: Math.random().toString(36).substring(7), + user_id: user?.id || 'guest', + title, + url: url.startsWith('http') ? url : `https://${url}`, + icon: 'sparkles', + }); + router.back(); + } catch (error) { + console.error('Failed to save shortcut:', error); + Alert.alert('Error', 'Failed to save shortcut'); + } finally { + setLoading(false); + } + }; + + return ( + + + + + router.back()}> + + + Add Shortcut + + {loading ? ( + + ) : ( + + )} + + + + + + SHORTCUT DETAILS + + + DISPLAY NAME + + + + + + + + URL OR APP LINK + + + + + + + + + + + Shortcuts will appear in your Ecosystem Tools section for quick access to your external workspaces. + + + + + + + ); +} + +const createStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: SPACING.lg, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + '4D', + }, + headerTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + }, + content: { + padding: SPACING.lg, + }, + section: { + gap: SPACING.lg, + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1.5, + marginBottom: 4, + }, + inputGroup: { + gap: 8, + }, + label: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.outline, + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + '4D', + paddingHorizontal: 12, + }, + inputIcon: { + marginRight: 10, + }, + input: { + flex: 1, + height: 52, + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + }, + infoCard: { + flexDirection: 'row', + backgroundColor: colors.primaryContainer + '40', + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + gap: 16, + borderWidth: 1, + borderColor: colors.primaryContainer, + marginTop: SPACING.xxl, + }, + infoText: { + flex: 1, + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + lineHeight: 20, + }, +}); diff --git a/src/app/add-task.tsx b/src/app/add-task.tsx new file mode 100644 index 0000000..93d499e --- /dev/null +++ b/src/app/add-task.tsx @@ -0,0 +1,280 @@ +import React, { useState, useMemo } from 'react'; +import { StyleSheet, View, Text, TextInput, TouchableOpacity, ScrollView, Alert, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { X, Save, Type, Tag, Target } from 'lucide-react-native'; +import { SPACING, FONTS, ROUNDNESS } from '@/src/constants/Theme'; +import { useTheme } from '@/src/hooks/useTheme'; +import { performMutation } from '@/src/lib/sync'; +import { useRouter } from 'expo-router'; +import { useAuth } from '@/src/hooks/useAuth'; +import { getDb } from '@/src/db/database'; +import * as Haptics from 'expo-haptics'; + +import { getLocalDateString } from '@/src/lib/date-utils'; + +const toMinutes = (time: string) => { + const [h, m] = time.split(':').map(Number); + return h * 60 + m; +}; + +const toTimeString = (minutes: number) => { + const h = Math.floor(minutes / 60); + const m = minutes % 60; + return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; +}; + +export default function AddTaskScreen() { + const { colors, sprintDuration } = useTheme(); + const { user } = useAuth(); + const styles = useMemo(() => createStyles(colors), [colors]); + const router = useRouter(); + + const [title, setTitle] = useState(''); + const [tag, setTag] = useState(''); + const [sessions, setSessions] = useState('1'); + const [loading, setLoading] = useState(false); + + const handleSave = async () => { + if (!title.trim()) { + Alert.alert('Error', 'Please provide a title for your task'); + return; + } + + const estSessions = parseInt(sessions) || 1; + const taskId = Math.random().toString(36).substring(7); + const userId = user?.id || 'guest'; + const today = getLocalDateString(); + + setLoading(true); + try { + // 1. Save the Task + await performMutation('tasks', 'INSERT', { + id: taskId, + user_id: userId, + title: title.trim(), + status: 'todo', + estimated_sessions: estSessions, + completed_sessions: 0, + tag: tag.trim() || 'General', + todos: '[]', + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }); + + // 2. Automatically Add to Schedule + const db = await getDb(); + const existingSchedule = await db.getFirstAsync<{id: string, time_blocks: string}>( + "SELECT id, time_blocks FROM schedules WHERE date = ? AND (user_id = ? OR user_id IS NULL)", + [today, userId] + ); + + let blocks = []; + let scheduleId = Math.random().toString(36).substring(7); + + if (existingSchedule) { + try { + blocks = JSON.parse(existingSchedule.time_blocks); + scheduleId = existingSchedule.id; + } catch (e) { blocks = []; } + } + + // Find first available slot after 9 AM + let currentStart = toMinutes("09:00"); + const duration = sprintDuration; + + const sortedBlocks = [...blocks].sort((a, b) => a.start.localeCompare(b.start)); + + for (const block of sortedBlocks) { + const blockStart = toMinutes(block.start); + if (blockStart >= currentStart + duration) { + break; + } + currentStart = Math.max(currentStart, toMinutes(block.end)); + } + + const newBlock = { + start: toTimeString(currentStart), + end: toTimeString(currentStart + duration), + task: title.trim(), + type: 'deep-work', + todos: [] + }; + + const updatedBlocks = [...blocks, newBlock].sort((a, b) => a.start.localeCompare(b.start)); + + await performMutation('schedules', existingSchedule ? 'UPDATE' : 'INSERT', { + id: scheduleId, + user_id: userId, + date: today, + time_blocks: JSON.stringify(updatedBlocks) + }); + + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + router.back(); + } catch (error) { + console.error('Failed to save task/schedule:', error); + Alert.alert('Error', 'Failed to save task'); + } finally { + setLoading(false); + } + }; + + return ( + + + + + router.back()}> + + + New Task + + {loading ? ( + + ) : ( + + )} + + + + + + TASK DETAILS + + + WHAT ARE YOU WORKING ON? + + + + + + + + TAG / CATEGORY + + + + + + + + ESTIMATED SESSIONS (25m each) + + + + + + + + + + Break large tasks into small, actionable chunks to maintain flow and momentum. + + + + + + + ); +} + +const createStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: SPACING.lg, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + '4D', + }, + headerTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + }, + content: { + padding: SPACING.lg, + }, + section: { + gap: SPACING.xl, + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1.5, + marginBottom: 4, + }, + inputGroup: { + gap: 8, + }, + label: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.outline, + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + '4D', + paddingHorizontal: 12, + }, + inputIcon: { + marginRight: 10, + }, + input: { + flex: 1, + height: 52, + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + }, + infoCard: { + backgroundColor: colors.primaryContainer + '40', + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.primaryContainer, + marginTop: SPACING.xxl, + alignItems: 'center', + }, + infoText: { + fontFamily: FONTS.body, + fontSize: 14, + color: colors.onSurfaceVariant, + textAlign: 'center', + lineHeight: 20, + }, +}); diff --git a/src/app/index.tsx b/src/app/index.tsx new file mode 100644 index 0000000..ac11cf9 --- /dev/null +++ b/src/app/index.tsx @@ -0,0 +1,23 @@ +import { Redirect } from 'expo-router'; +import { useAuth } from '../hooks/useAuth'; +import { View, ActivityIndicator } from 'react-native'; +import { useTheme } from '../hooks/useTheme'; + +export default function Index() { + const { isAuthenticated, loading } = useAuth(); + const { colors } = useTheme(); + + if (loading) { + return ( + + + + ); + } + + if (!isAuthenticated) { + return ; + } + + return ; +} diff --git a/src/app/login.tsx b/src/app/login.tsx new file mode 100644 index 0000000..cbc442f --- /dev/null +++ b/src/app/login.tsx @@ -0,0 +1,313 @@ +import React, { useState, useMemo } from 'react'; +import { StyleSheet, View, Text, TextInput, TouchableOpacity, ScrollView, Image, ActivityIndicator, Alert, KeyboardAvoidingView, Platform } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Mail, Lock, LogIn, UserPlus, Sparkles, ArrowRight } from 'lucide-react-native'; +import { SPACING, FONTS, ROUNDNESS } from '@/src/constants/Theme'; +import { useTheme } from '@/src/hooks/useTheme'; +import { supabase } from '@/src/lib/supabase'; +import { useRouter } from 'expo-router'; + +export default function LoginScreen() { + const { colors } = useTheme(); + const styles = useMemo(() => createStyles(colors), [colors]); + const router = useRouter(); + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [isSignUp, setIsSignUp] = useState(false); + + async function handleAuth() { + if (!email || !password) { + Alert.alert('Error', 'Please fill in all fields'); + return; + } + + setLoading(true); + try { + if (isSignUp) { + const { error } = await supabase.auth.signUp({ + email, + password, + }); + if (error) throw error; + Alert.alert('Success', 'Check your email for the confirmation link!'); + } else { + const { data: authData, error: signInError } = await supabase.auth.signInWithPassword({ + email, + password, + }); + if (signInError) throw signInError; + + // Ensure profile exists + if (authData?.user) { + const { error: profileError } = await supabase + .from('profiles') + .upsert({ + id: authData.user.id, + email: authData.user.email, + updated_at: new Date().toISOString() + }); + + if (profileError) { + console.error('Error ensuring profile exists:', profileError.message); + } + } + + router.replace('/(tabs)/'); + } + } catch (error: any) { + Alert.alert('Auth Error', error.message); + } finally { + setLoading(false); + } + } + + return ( + + + + + + + + + {isSignUp ? 'Join Batsir' : 'Welcome Back'} + + {isSignUp ? 'Start your journey to atomic efficiency.' : 'Find your flow state and continue building.'} + + + + + + EMAIL ADDRESS + + + + + + + + PASSWORD + + + + + + + + {loading ? ( + + ) : ( + <> + {isSignUp ? 'Create Account' : 'Sign In'} + {isSignUp ? : } + + )} + + + setIsSignUp(!isSignUp)} + > + + {isSignUp ? 'Already have an account? Sign In' : "Don't have an account? Sign Up"} + + + + + {!isSignUp && ( + + + + + + Batsirai Integration + Your personal flow assistant is ready to help you optimize your schedule. + + + )} + + router.replace('/(tabs)/')}> + Continue as Guest + + + + + + + ); +} + +const createStyles = (colors: any) => StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + scrollContent: { + padding: SPACING.xl, + flexGrow: 1, + justifyContent: 'center', + }, + header: { + marginBottom: SPACING.xxl, + alignItems: 'center', + }, + logoContainer: { + marginBottom: SPACING.lg, + }, + logoImage: { + height: 48, + width: 180, + }, + title: { + fontFamily: FONTS.headline, + fontSize: 32, + color: colors.onSurface, + textAlign: 'center', + marginBottom: 8, + }, + subtitle: { + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurfaceVariant, + textAlign: 'center', + lineHeight: 22, + paddingHorizontal: 20, + }, + form: { + gap: SPACING.lg, + marginBottom: SPACING.xl, + }, + inputGroup: { + gap: 8, + }, + label: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1.5, + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + '4D', + paddingHorizontal: 12, + }, + inputIcon: { + marginRight: 10, + }, + input: { + flex: 1, + height: 52, + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + }, + primaryBtn: { + backgroundColor: colors.primary, + height: 56, + borderRadius: ROUNDNESS.full, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 12, + marginTop: 10, + elevation: 2, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 8, + }, + primaryBtnText: { + color: colors.onPrimary, + fontFamily: FONTS.labelSm, + fontSize: 16, + }, + switchBtn: { + alignItems: 'center', + padding: 10, + }, + switchBtnText: { + fontFamily: FONTS.label, + fontSize: 14, + color: colors.primary, + }, + featureHighlight: { + flexDirection: 'row', + backgroundColor: colors.primaryContainer + '40', + padding: SPACING.lg, + borderRadius: ROUNDNESS.lg, + gap: 16, + borderWidth: 1, + borderColor: colors.primaryContainer, + marginBottom: SPACING.xl, + }, + featureIcon: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: colors.surface, + alignItems: 'center', + justifyContent: 'center', + }, + featureContent: { + flex: 1, + }, + featureTitle: { + fontFamily: FONTS.headline, + fontSize: 16, + color: colors.onSurface, + marginBottom: 2, + }, + featureDesc: { + fontFamily: FONTS.body, + fontSize: 13, + color: colors.onSurfaceVariant, + lineHeight: 18, + }, + guestBtn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + padding: 10, + }, + guestBtnText: { + fontFamily: FONTS.label, + fontSize: 14, + color: colors.outline, + }, +}); diff --git a/src/app/menu.tsx b/src/app/menu.tsx new file mode 100644 index 0000000..dc319c1 --- /dev/null +++ b/src/app/menu.tsx @@ -0,0 +1,260 @@ +import { FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import * as Haptics from "expo-haptics"; +import { useRouter } from "expo-router"; +import { + Activity, + ChevronRight, + Clock, + Save, + Sparkles, + Target, + X, + History +} from "lucide-react-native"; +import React, { useMemo, useState } from "react"; +import { + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +export default function MenuModal() { + const { + colors, + identityAnchor, + updateIdentityAnchor, + focusGoal, + updateFocusGoal, + } = useTheme(); + const router = useRouter(); + const styles = useMemo(() => createStyles(colors), [colors]); + + const [newAnchor, setNewAnchor] = useState(identityAnchor); + const [newGoal, setNewGoal] = useState(focusGoal.toString()); + + const handleSaveSettings = async () => { + try { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + await updateIdentityAnchor(newAnchor); + const goalNum = parseInt(newGoal); + if (!isNaN(goalNum)) { + await updateFocusGoal(goalNum); + } + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } catch (e) { + console.error(e); + } + }; + + const menuItems = [ + { + title: "Sprint", + subtitle: "Start a focused work session", + icon: , + route: "/sprint" as const, + }, + { + title: "History", + subtitle: "Browse past activities & inventory", + icon: , + route: "/history" as const, + }, + { + title: "Assistant", + subtitle: "Chat with Batsirai AI", + icon: , + route: "/aa_ai" as const, + }, + { + title: "Habits", + subtitle: "Manage your daily evolution", + icon: , + route: "/hh_habits" as const, + }, + ]; + + const navigateTo = (route: any) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.replace(route); + }; + + return ( + + + + Navigation + router.back()} + style={styles.closeBtn} + > + + + + + + + MAIN TOOLS + {menuItems.map((item, index) => ( + navigateTo(item.route)} + > + + {item.icon} + + {item.title} + {item.subtitle} + + + + + ))} + + + + BATSIRAI / ECOSYSTEM + + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: SPACING.lg, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "4D", + }, + headerTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + color: colors.onSurface, + }, + closeBtn: { + padding: 4, + }, + content: { + padding: SPACING.lg, + }, + section: { + gap: 8, + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1.5, + marginBottom: 8, + }, + settingsCard: { + backgroundColor: colors.surface, + padding: 16, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + gap: 16, + }, + inputGroup: { + gap: 8, + }, + rowAlign: { + flexDirection: "row", + alignItems: "center", + gap: 8, + }, + inputLabel: { + fontFamily: FONTS.labelSm, + fontSize: 10, + color: colors.outline, + }, + input: { + backgroundColor: colors.surfaceVariant + "40", + borderRadius: ROUNDNESS.md, + paddingHorizontal: 12, + paddingVertical: 10, + fontFamily: FONTS.body, + fontSize: 16, + color: colors.onSurface, + borderWidth: 1, + borderColor: colors.outlineVariant + "26", + }, + saveBtn: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 10, + backgroundColor: colors.primary, + paddingVertical: 12, + borderRadius: ROUNDNESS.md, + marginTop: 8, + }, + saveBtnText: { + color: colors.onPrimary, + fontFamily: FONTS.labelSm, + fontSize: 14, + }, + menuItem: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + padding: 16, + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + marginBottom: 8, + }, + menuItemLeft: { + flexDirection: "row", + alignItems: "center", + gap: 16, + }, + iconBg: { + width: 44, + height: 44, + borderRadius: ROUNDNESS.md, + backgroundColor: colors.primaryContainer + "40", + alignItems: "center", + justifyContent: "center", + }, + menuItemTitle: { + fontFamily: FONTS.headline, + fontSize: 16, + color: colors.onSurface, + }, + menuItemSubtitle: { + fontFamily: FONTS.body, + fontSize: 12, + color: colors.onSurfaceVariant, + marginTop: 2, + }, + footer: { + marginTop: 40, + alignItems: "center", + paddingBottom: 40, + }, + footerText: { + fontFamily: FONTS.label, + fontSize: 10, + color: colors.outline, + letterSpacing: 2, + }, + }); diff --git a/src/app/modal.tsx b/src/app/modal.tsx new file mode 100644 index 0000000..bfdda49 --- /dev/null +++ b/src/app/modal.tsx @@ -0,0 +1,662 @@ +import { AccentKey, FONTS, ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { ThemeMode, useTheme } from "@/src/hooks/useTheme"; +import * as Haptics from "expo-haptics"; +import { useRouter } from "expo-router"; +import { + Check, + Clock, + Edit3, + LogOut, + Monitor, + Moon, + RefreshCw, + Save, + Sun, + Target, + X, + Shield, + Database, + Fingerprint +} from "lucide-react-native"; +import React, { useMemo, useState } from "react"; +import { + ActivityIndicator, + Alert, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, + KeyboardAvoidingView, + Platform +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useAuth } from "../hooks/useAuth"; +import { pullFromServer, syncWithSupabase } from "../lib/sync"; + +export default function SettingsModal() { + const { + colors, + accentKey, + updateAccent, + availableAccents, + themeMode, + updateThemeMode, + focusGoal, + updateFocusGoal, + sprintDuration, + updateSprintDuration, + displayName, + updateDisplayName, + identityAnchor, + updateIdentityAnchor, + } = useTheme(); + + const [newName, setNewName] = useState(displayName); + const [newIdentity, setNewIdentity] = useState(identityAnchor); + const [newGoal, setNewGoal] = useState(focusGoal.toString()); + const [newSprintDuration, setNewSprintDuration] = useState( + sprintDuration.toString(), + ); + + const [isEditingAccount, setIsEditingAccount] = useState(false); + const [isEditingIdentity, setIsEditingIdentity] = useState(false); + const [isEditingGoals, setIsEditingGoals] = useState(false); + + const accountDirty = newName.trim() !== displayName.trim(); + const identityDirty = newIdentity.trim() !== identityAnchor.trim(); + const goalsDirty = + newGoal.trim() !== focusGoal.toString() || + newSprintDuration.trim() !== sprintDuration.toString(); + + const { signOut, user } = useAuth(); + const router = useRouter(); + const styles = useMemo(() => createStyles(colors), [colors]); + const [syncing, setSyncing] = useState(false); + + const handleSaveIdentity = async () => { + if (!identityDirty) return; + try { + await updateIdentityAnchor(newIdentity); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + setIsEditingIdentity(false); + } catch (e) { + Alert.alert("Error", "Unable to save identity anchor."); + } + }; + + const handleSaveAccount = async () => { + if (!accountDirty) return; + try { + await updateDisplayName(newName); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + setIsEditingAccount(false); + } catch (e) { + Alert.alert("Error", "Unable to save display name."); + } + }; + + const handleSaveGoals = async () => { + if (!goalsDirty) return; + const goalNum = parseInt(newGoal, 10); + const sprintNum = parseInt(newSprintDuration, 10); + + if (isNaN(goalNum) || isNaN(sprintNum)) { + Alert.alert("Error", "Please enter valid numbers."); + return; + } + + try { + await updateFocusGoal(goalNum); + await updateSprintDuration(sprintNum); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + setIsEditingGoals(false); + } catch (e) { + Alert.alert("Error", "Unable to save goals."); + } + }; + + const handleSignOut = async () => { + Alert.alert("Sign Out", "Are you sure you want to sign out?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Sign Out", + style: "destructive", + onPress: async () => { + await signOut(); + router.replace("/login"); + }, + }, + ]); + }; + + const handleManualSync = async () => { + setSyncing(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + try { + await syncWithSupabase(); + await pullFromServer(); + Alert.alert("Cloud Sync Successful", "Your flow is now synchronized across all devices."); + } catch (e) { + Alert.alert("Sync Error", "Could not connect to the cloud architect."); + } finally { + setSyncing(false); + } + }; + + const handleResetData = () => { + Alert.alert( + "Hard Reset", + "This will clear all local data. Cloud data will remain safe. Proceed?", + [ + { text: "Cancel", style: "cancel" }, + { + text: "Reset", + style: "destructive", + onPress: async () => { + Alert.alert("Reset Complete", "Local data has been purged."); + } + } + ] + ); + }; + + return ( + + + + + System Configuration + router.back()} + > + + + + + + {/* Identity Section */} + + + + IDENTITY PRINCIPLE + + + This is the "Identity Anchor" that guides your habits. Define who you are becoming. + + + + + + {!isEditingIdentity ? ( + setIsEditingIdentity(true)}> + + Refine Identity + + ) : ( + <> + { setIsEditingIdentity(false); setNewIdentity(identityAnchor); }}> + + Cancel + + + + Save Anchor + + + )} + + + + {/* Account Section */} + + + + ACCOUNT SECURITY + + + + {user?.email || "Guest User"} + + + + + + + + {!isEditingAccount ? ( + setIsEditingAccount(true)}> + + Update Profile + + ) : ( + + + Save Profile + + )} + + + + {/* Productivity Goals */} + + + + PRODUCTIVITY ARCHITECTURE + + + + + + Daily Focus Goal + Number of sessions per day + + + + + + + + + Sprint Duration + Minutes per session + + + + + + {!isEditingGoals ? ( + setIsEditingGoals(true)}> + + Modify Goals + + ) : ( + + + Apply Goals + + )} + + + + {/* Visual Style */} + + VISUAL INTERFACE + + {(['light', 'dark', 'system'] as ThemeMode[]).map((mode) => ( + { + updateThemeMode(mode); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + {mode === 'light' ? : + mode === 'dark' ? : + } + + {mode.charAt(0).toUpperCase() + mode.slice(1)} + + + ))} + + + + {(Object.keys(availableAccents) as AccentKey[]).map((key) => { + const accent = availableAccents[key]; + const isSelected = accentKey === key; + return ( + { + updateAccent(key); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + {isSelected && } + + + {accent.label} + + + ); + })} + + + + {/* System & Sync */} + + + + CORE ENGINE + + + + + Cloud Architecture Sync + Last verified: Just now + + {syncing ? : } + + + + + Purge Local Cache + Hard reset database + + + + + + + + Batsirai Productivity Planner v1.2.0 + Engineered for Focus + + + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: SPACING.lg, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "4D", + }, + title: { + fontFamily: FONTS.headline, + fontSize: 22, + color: colors.onSurface, + }, + closeBtn: { + padding: 4, + }, + content: { + padding: SPACING.lg, + }, + section: { + marginBottom: SPACING.xxl, + }, + sectionHeaderRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginBottom: 8, + }, + sectionLabel: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.primary, + letterSpacing: 1.5, + }, + sectionDesc: { + fontFamily: FONTS.body, + fontSize: 13, + color: colors.onSurfaceVariant, + lineHeight: 18, + marginBottom: 12, + }, + accountInfo: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: colors.surface, + padding: 16, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + accountDetails: { + flex: 1, + }, + nameInput: { + fontFamily: FONTS.labelSm, + fontSize: 15, + color: colors.primary, + marginTop: 4, + }, + signOutBtn: { + padding: 8, + }, + inputGroup: { + gap: 8, + }, + input: { + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + borderRadius: ROUNDNESS.md, + paddingHorizontal: 16, + paddingVertical: 12, + fontFamily: FONTS.body, + color: colors.onSurface, + fontSize: 15, + }, + inputDisabled: { + opacity: 0.6, + backgroundColor: colors.surfaceVariant + '40', + }, + actionRow: { + flexDirection: 'row', + gap: 12, + marginTop: 12, + }, + actionBtn: { + flex: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + paddingVertical: 12, + borderRadius: ROUNDNESS.md, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.outlineVariant + "80", + }, + actionBtnActive: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + actionBtnText: { + fontFamily: FONTS.label, + color: colors.onSurface, + fontSize: 13, + }, + actionBtnTextActive: { + color: colors.onPrimary, + }, + goalRow: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + backgroundColor: colors.surface, + padding: 14, + borderRadius: ROUNDNESS.lg, + marginBottom: 8, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + goalInfo: { + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + goalTitle: { + fontFamily: FONTS.headline, + fontSize: 15, + color: colors.onSurface, + }, + goalDesc: { + fontFamily: FONTS.body, + fontSize: 12, + color: colors.onSurfaceVariant, + }, + goalInput: { + width: 60, + height: 40, + backgroundColor: colors.surfaceVariant + "80", + borderRadius: ROUNDNESS.sm, + textAlign: "center", + fontFamily: FONTS.labelSm, + color: colors.primary, + fontSize: 18, + }, + themeToggleGrid: { + flexDirection: "row", + gap: 10, + marginBottom: SPACING.xl, + }, + themeOption: { + flex: 1, + paddingVertical: 16, + alignItems: "center", + justifyContent: "center", + borderRadius: ROUNDNESS.lg, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + gap: 8, + }, + themeLabel: { + fontFamily: FONTS.label, + fontSize: 12, + color: colors.onSurfaceVariant, + }, + accentGrid: { + flexDirection: "row", + flexWrap: "wrap", + gap: 10, + }, + accentCard: { + width: "31%", + backgroundColor: colors.surface, + padding: 10, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + alignItems: "center", + gap: 8, + marginBottom: 4, + }, + accentPreview: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: "center", + justifyContent: "center", + }, + accentLabel: { + fontFamily: FONTS.label, + fontSize: 11, + color: colors.onSurfaceVariant, + }, + systemCard: { + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + overflow: 'hidden', + }, + systemItem: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: 16, + borderBottomWidth: 1, + borderBottomColor: colors.outlineVariant + "33", + }, + systemItemInfo: { + flex: 1, + }, + menuItemText: { + fontFamily: FONTS.body, + fontSize: 15, + color: colors.onSurface, + }, + menuItemValue: { + fontFamily: FONTS.label, + fontSize: 12, + color: colors.onSurfaceVariant, + marginTop: 2, + }, + footer: { + alignItems: "center", + marginTop: SPACING.xl, + paddingBottom: 60, + }, + versionText: { + fontFamily: FONTS.label, + fontSize: 11, + color: colors.outline, + letterSpacing: 0.5, + textTransform: 'uppercase', + marginBottom: 4, + }, + }); diff --git a/src/app/reader/[id].tsx b/src/app/reader/[id].tsx new file mode 100644 index 0000000..b0928c0 --- /dev/null +++ b/src/app/reader/[id].tsx @@ -0,0 +1,169 @@ +import React, { useEffect, useState, useCallback, useRef } from 'react'; +import { View, ActivityIndicator, StyleSheet, Alert } from 'react-native'; +import { useLocalSearchParams, useRouter, Stack } from 'expo-router'; +import { getDb } from '@/src/db/database'; +import { performMutation } from '@/src/lib/sync'; +import { resolveFileUri, downloadBook } from '@/src/lib/file-utils'; +import PdfReader from '@/src/components/Library/PdfReader'; +import { useTheme } from '@/src/hooks/useTheme'; +import { ThemedText } from '@/components/themed-text'; +import * as Haptics from 'expo-haptics'; + +interface Book { + id: string; + title: string; + file_uri: string; + current_page: number; + total_pages: number; + status: string; +} + +export default function ReaderScreen() { + const { id } = useLocalSearchParams<{ id: string }>(); + const router = useRouter(); + const { colors } = useTheme(); + + const [book, setBook] = useState(null); + const [loading, setLoading] = useState(true); + const [downloading, setDownloading] = useState(false); + const [localUri, setLocalUri] = useState(null); + const [currentPage, setCurrentPage] = useState(0); + const [startPage, setStartPage] = useState(0); + const [totalPages, setTotalPages] = useState(0); + const [sessionSeconds, setSessionSeconds] = useState(0); + + const sessionStartTime = useRef(Date.now()); + + useEffect(() => { + loadBook(); + }, [id]); + + const loadBook = async () => { + try { + const db = await getDb(); + const bookData = await db.getFirstAsync( + 'SELECT * FROM books WHERE id = ?', + [id] + ); + + if (bookData) { + setBook(bookData); + setCurrentPage(bookData.current_page || 0); + setStartPage(bookData.current_page || 0); + setTotalPages(bookData.total_pages || 0); + + // Ensure file is local + try { + setDownloading(true); + const uri = await downloadBook(bookData.file_uri); + setLocalUri(uri); + } catch (e) { + console.error('Failed to ensure local file:', e); + // Fallback to resolved URI (might be a remote URL if docDir was null) + setLocalUri(resolveFileUri(bookData.file_uri)); + } finally { + setDownloading(false); + } + } else { + Alert.alert('Error', 'Book not found'); + router.back(); + } + } catch (error) { + console.error('Failed to load book:', error); + Alert.alert('Error', 'Failed to load book'); + router.back(); + } finally { + setLoading(false); + } + }; + + const handleClose = async () => { + if (!book) return; + + const durationSeconds = Math.floor((Date.now() - sessionStartTime.current) / 1000); + const pagesRead = Math.max(0, currentPage - startPage); + + try { + // Update book progress + await performMutation('books', 'UPDATE', { + id: book.id, + current_page: currentPage, + total_pages: totalPages, + status: totalPages > 0 && currentPage >= totalPages - 1 ? 'finished' : 'reading', + updated_at: new Date().toISOString(), + }); + + // Log reading session + if (pagesRead > 0 || durationSeconds > 30) { + await performMutation('reading_logs', 'INSERT', { + id: Math.random().toString(36).substring(7), + book_id: book.id, + start_page: startPage, + end_page: currentPage, + pages_read: pagesRead, + duration_seconds: durationSeconds, + duration_minutes: durationSeconds / 60, + logged_at: new Date().toISOString(), + }); + } + + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + router.back(); + } catch (error) { + console.error('Failed to save progress:', error); + router.back(); + } + }; + + const handlePageChange = useCallback((page: number, total: number) => { + setCurrentPage(page); + if (total > 0 && total !== totalPages) { + setTotalPages(total); + } + }, [totalPages]); + + if (loading || (downloading && !localUri)) { + return ( + + + {downloading && ( + + Downloading your book... + + )} + + ); + } + + if (!book || !localUri) return null; + + return ( + + + { + // Future: Open note editor + console.log('Add note for page:', page); + }} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#000', + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/src/components/AutoHideFloatingActionButton.tsx b/src/components/AutoHideFloatingActionButton.tsx new file mode 100644 index 0000000..0fbbe66 --- /dev/null +++ b/src/components/AutoHideFloatingActionButton.tsx @@ -0,0 +1,127 @@ +import { useTheme } from "@/src/hooks/useTheme"; +import { BlurView } from "expo-blur"; +import * as Haptics from "expo-haptics"; +import { LayoutDashboard } from "lucide-react-native"; +import React, { useCallback, useMemo, useRef } from "react"; +import { Animated, Platform, Pressable, StyleSheet, View } from "react-native"; + +type Props = { + onPress: () => void; + accentColor: string; + label?: string; + position?: "center" | "right"; +}; + +export default function AutoHideFloatingActionButton({ + onPress, + accentColor, + label = "Day", + position = "center", +}: Props) { + const { isDark, colors } = useTheme(); + const scaleAnim = useRef(new Animated.Value(1)).current; + + const onPressIn = useCallback(() => { + // Requirement: Animated scale on press (0.95 -> 1.0) + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + Animated.spring(scaleAnim, { + toValue: 0.95, + useNativeDriver: true, + friction: 8, + tension: 100, + }).start(); + }, [scaleAnim]); + + const onPressOut = useCallback(() => { + Animated.spring(scaleAnim, { + toValue: 1, + useNativeDriver: true, + friction: 8, + tension: 100, + }).start(); + }, [scaleAnim]); + + const animatedStyle = useMemo( + () => ({ transform: [{ scale: scaleAnim }] }), + [scaleAnim], + ); + + return ( + + [ + styles.pressable, + { opacity: pressed ? 0.95 : 1 }, + ]} + > + + + + + + + + ); +} + +const styles = StyleSheet.create({ + wrapper: { + // Requirement: Large border radius (24-28px) -> 28 for 56x56 + borderRadius: 30, + // Requirement: Slight elevation/shadow + shadowColor: "#000", + shadowOpacity: 0.2, + shadowRadius: 16, + shadowOffset: { width: 0, height: 8 }, + elevation: 10, + }, + pressable: { + borderRadius: 30, + overflow: "hidden", + }, + positionCenter: { + alignSelf: "center", + }, + positionRight: { + alignSelf: "flex-end", + marginRight: 20, + }, + fab: { + // Requirement: Circular (56x56 points) + width: 60, + height: 60, + borderRadius: 30, + borderWidth: 1, + justifyContent: "center", + alignItems: "center", + }, + iconContainer: { + justifyContent: "center", + alignItems: "center", + }, +}); diff --git a/src/components/AutoHideScrollView.tsx b/src/components/AutoHideScrollView.tsx new file mode 100644 index 0000000..4bb6388 --- /dev/null +++ b/src/components/AutoHideScrollView.tsx @@ -0,0 +1,25 @@ +import { useAutoHideOnScroll } from "@/src/hooks/useAutoHideTabBar"; +import React from "react"; +import { ScrollView, ScrollViewProps } from "react-native"; + +const AutoHideScrollView = React.forwardRef( + (props, ref) => { + const { onScroll, scrollEventThrottle } = useAutoHideOnScroll(); + + return ( + { + onScroll(event); + if (props.onScroll) { + props.onScroll(event); + } + }} + /> + ); + }, +); + +export default AutoHideScrollView; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..ae72b68 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,70 @@ +import { FONTS, SPACING, ThemeColors } from "@/src/constants/Theme"; +import React from "react"; +import { StyleSheet, Text, View } from "react-native"; + +interface ErrorBoundaryProps { + children: React.ReactNode; + colors: ThemeColors; +} + +interface ErrorBoundaryState { + hasError: boolean; + message: string; +} + +export class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, message: "" }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, message: error.message }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error("Chat error boundary caught:", error, errorInfo); + } + + render() { + const { hasError, message } = this.state; + const { colors, children } = this.props; + + if (!hasError) { + return children; + } + + return ( + + + Something went wrong. + + + {message} + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + padding: SPACING.lg, + }, + title: { + fontFamily: FONTS.headline, + fontSize: 20, + marginBottom: SPACING.sm, + }, + details: { + fontFamily: FONTS.body, + fontSize: 14, + textAlign: "center", + }, +}); diff --git a/src/components/FloatingAIDock.tsx b/src/components/FloatingAIDock.tsx new file mode 100644 index 0000000..12d4c3b --- /dev/null +++ b/src/components/FloatingAIDock.tsx @@ -0,0 +1,141 @@ +import { useTheme } from "@/src/hooks/useTheme"; +import { BlurView } from "expo-blur"; +import * as Haptics from "expo-haptics"; +import { usePathname, useRouter } from "expo-router"; +import { Sparkles } from "lucide-react-native"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + Animated, + Keyboard, + Platform, + Pressable, + StyleSheet, + View +} from "react-native"; + +export default function FloatingAIDock() { + const { colors, isDark } = useTheme(); + const router = useRouter(); + const pathname = usePathname(); + + const [isVisible, setIsVisible] = useState(true); + const visibilityAnim = useRef(new Animated.Value(1)).current; + + // Keyboard awareness - hide when typing + useEffect(() => { + const showSubscription = Keyboard.addListener( + Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow", + () => setIsVisible(false) + ); + const hideSubscription = Keyboard.addListener( + Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide", + () => setIsVisible(true) + ); + + return () => { + showSubscription.remove(); + hideSubscription.remove(); + }; + }, []); + + useEffect(() => { + Animated.spring(visibilityAnim, { + toValue: isVisible ? 1 : 0, + useNativeDriver: true, + friction: 8, + tension: 40, + }).start(); + }, [isVisible, visibilityAnim]); + + const navigateToAI = useCallback(() => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + router.push("/aa_ai"); + }, [router]); + + const isActive = pathname === "/aa_ai"; + + const dockStyle = useMemo(() => [ + styles.dockContainer, + { + right: 16, + top: "55%", // Slightly below center + transform: [ + { scale: visibilityAnim }, + { translateX: visibilityAnim.interpolate({ + inputRange: [0, 1], + outputRange: [100, 0], + }) + }, + ], + opacity: visibilityAnim, + } + ], [visibilityAnim]); + + // Don't show the launcher if we're already on the AI screen + if (isActive) return null; + + return ( + + [ + styles.pressable, + { transform: [{ scale: pressed ? 0.92 : 1 }] } + ]} + > + + + + + + + + ); +} + +const styles = StyleSheet.create({ + dockContainer: { + position: "absolute", + zIndex: 1000, + width: 64, + height: 64, + shadowColor: "#000", + shadowOpacity: 0.2, + shadowRadius: 15, + shadowOffset: { width: 0, height: 8 }, + elevation: 20, + }, + pressable: { + width: "100%", + height: "100%", + }, + blurContainer: { + flex: 1, + borderRadius: 32, + overflow: "hidden", + borderWidth: 1.5, + alignItems: "center", + justifyContent: "center", + }, + iconWrapper: { + alignItems: "center", + justifyContent: "center", + // Subtle glow effect + shadowColor: "#FFF", + shadowOpacity: 0.1, + shadowRadius: 10, + } +}); diff --git a/src/components/GlassBottomTabBar.tsx b/src/components/GlassBottomTabBar.tsx new file mode 100644 index 0000000..4d5d14f --- /dev/null +++ b/src/components/GlassBottomTabBar.tsx @@ -0,0 +1,309 @@ +import AutoHideFloatingActionButton from "@/src/components/AutoHideFloatingActionButton"; +import { FONTS } from "@/src/constants/Theme"; +import { useTabBarVisibility } from "@/src/hooks/useAutoHideTabBar"; +import { useTheme } from "@/src/hooks/useTheme"; +import { BottomTabBarProps } from "@react-navigation/bottom-tabs"; +import { BlurView } from "expo-blur"; +import * as Haptics from "expo-haptics"; +import React, { useEffect, useMemo, useRef } from "react"; +import { + Animated, + Platform, + Pressable, + StyleSheet, + Text, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +const MAIN_TABS = ["index", "calendar", "library"]; + +export default function GlassBottomTabBar(props: BottomTabBarProps) { + const { state, descriptors, navigation } = props; + const { colors, isDark } = useTheme(); + + const { tabBarVisible, showTabBar } = useTabBarVisibility(); + + const insets = useSafeAreaInsets(); + + const currentRouteName = state.routes[state.index].name; + + const animated = useRef(new Animated.Value(tabBarVisible ? 0 : 1)).current; + + useEffect(() => { + Animated.spring(animated, { + toValue: tabBarVisible ? 0 : 1, + damping: 18, + stiffness: 180, + mass: 0.9, + useNativeDriver: true, + }).start(); + }, [tabBarVisible, animated]); + + const containerStyle = useMemo( + () => [ + styles.container, + { + bottom: insets.bottom + 12, + transform: [ + { + translateY: animated.interpolate({ + inputRange: [0, 1], + outputRange: [0, 140], + }), + }, + ], + opacity: animated.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0], + }), + }, + ], + [animated, insets.bottom], + ); + + const fabStyle = useMemo( + () => ({ + transform: [ + { + translateY: animated.interpolate({ + inputRange: [0, 1], + outputRange: [160, 0], + }), + }, + { + scale: animated.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0.7], + }), + }, + ], + opacity: animated.interpolate({ + inputRange: [0, 1], + outputRange: [0, 1], + }), + }), + [animated], + ); + + const indexRoute = state.routes.find((route) => route.name === "index"); + + const onMostImportantPress = () => { + if (currentRouteName === "index" && indexRoute) { + const event = navigation.emit({ + type: "tabPress", + target: indexRoute.key, + canPreventDefault: true, + }); + + if (!event.defaultPrevented) { + navigation.navigate("index"); + } + } else { + navigation.navigate("index"); + } + + showTabBar(); + }; + + if (currentRouteName === "aa_ai") { + return null; + } + + const visibleRoutes = state.routes.filter((r) => MAIN_TABS.includes(r.name)); + + const indexRouteKey = visibleRoutes.find((r) => r.name === "index")?.key; + const indexActiveColor = + (descriptors[indexRouteKey || ""].options + .tabBarActiveTintColor as string) || colors.primary; + + return ( + <> + + + + {visibleRoutes.map((route) => { + const descriptor = descriptors[route.key]; + + const focused = currentRouteName === route.name; + + const activeColor = + (descriptor.options.tabBarActiveTintColor as string) || + colors.primary; + + const inactiveColor = + (descriptor.options.tabBarInactiveTintColor as string) || + colors.outline; + + const color = focused ? activeColor : inactiveColor; + + const label = descriptor.options.title || route.name; + + const icon = descriptor.options.tabBarIcon?.({ + focused, + color, + size: 24, + }); + + const onPress = () => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + + const event = navigation.emit({ + type: "tabPress", + target: route.key, + canPreventDefault: true, + }); + + if (!event.defaultPrevented) { + navigation.navigate(route.name); + } + }; + + return ( + + + {icon} + + + {label} + + + + ); + })} + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + position: "absolute", + alignSelf: "center", + zIndex: 100, + }, + + blurContainer: { + borderRadius: 999, + overflow: "hidden", + + borderWidth: 1, + + paddingHorizontal: 8, + paddingVertical: 8, + + shadowOpacity: 0.15, + shadowRadius: 20, + shadowOffset: { + width: 0, + height: 8, + }, + + elevation: 20, + }, + + inner: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 4, + }, + + tabWrapper: { + borderRadius: 999, + }, + + tabButton: { + minWidth: 76, + height: 44, + + borderRadius: 999, + + alignItems: "center", + justifyContent: "center", + + paddingHorizontal: 12, + }, + + activeTabButton: { + borderWidth: 1, + }, + + iconWrapper: { + marginBottom: 2, + alignItems: "center", + justifyContent: "center", + }, + + label: { + fontFamily: FONTS.label, + fontSize: 10, + letterSpacing: -0.1, + textAlign: "center", + lineHeight: 12, + }, + + fabHost: { + position: "absolute", + alignSelf: "center", + zIndex: 101, + }, +}); diff --git a/src/components/Library/AddBookModal.tsx b/src/components/Library/AddBookModal.tsx new file mode 100644 index 0000000..2aa6a3e --- /dev/null +++ b/src/components/Library/AddBookModal.tsx @@ -0,0 +1,178 @@ +import { ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { Upload, X } from "lucide-react-native"; +import React from "react"; +import { + ActivityIndicator, + KeyboardAvoidingView, + Modal, + Platform, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; + +interface AddBookModalProps { + visible: boolean; + onClose: () => void; + bookTitle: string; + onTitleChange: (text: string) => void; + bookAuthor: string; + onAuthorChange: (text: string) => void; + totalPages: string; + onPagesChange: (text: string) => void; + loading: boolean; + onAddBook: () => void; +} + +export default function AddBookModal({ + visible, + onClose, + bookTitle, + onTitleChange, + bookAuthor, + onAuthorChange, + totalPages, + onPagesChange, + loading, + onAddBook, +}: AddBookModalProps) { + const { colors } = useTheme(); + const styles = StyleSheet.create({ + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.5)", + justifyContent: "flex-end", + }, + modalContainer: { + maxHeight: "90%", + backgroundColor: colors.surface, + borderTopLeftRadius: ROUNDNESS.lg, + borderTopRightRadius: ROUNDNESS.lg, + }, + modalHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderBottomWidth: 1, + borderBottomColor: colors.surfaceVariant, + }, + modalTitle: { + fontSize: 16, + fontWeight: "700", + color: colors.onSurface, + }, + modalField: { + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + }, + modalLabel: { + fontSize: 10, + fontWeight: "600", + color: colors.outline, + marginBottom: SPACING.sm, + letterSpacing: 0.5, + }, + modalInput: { + borderWidth: 1, + borderColor: colors.surfaceVariant, + borderRadius: ROUNDNESS.md, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.md, + fontSize: 14, + color: colors.onSurface, + backgroundColor: colors.background, + }, + primaryBtn: { + backgroundColor: colors.primary, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderRadius: ROUNDNESS.md, + gap: SPACING.sm, + marginHorizontal: SPACING.lg, + marginVertical: SPACING.md, + }, + primaryBtnText: { + color: colors.onPrimary, + fontSize: 14, + fontWeight: "600", + letterSpacing: 0.5, + }, + }); + + return ( + + + + + Add New Book + + + + + + + TITLE + + + + AUTHOR + + + + TOTAL PAGES + + + + {loading ? ( + + ) : ( + <> + + Add to Library + + )} + + + + + + ); +} diff --git a/src/components/Library/BookCard.tsx b/src/components/Library/BookCard.tsx new file mode 100644 index 0000000..78c0d9d --- /dev/null +++ b/src/components/Library/BookCard.tsx @@ -0,0 +1,128 @@ +import { ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { Book as BookIcon, Play } from "lucide-react-native"; +import React from "react"; +import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; + +interface Book { + id: string; + title: string; + author: string; + total_pages: number; + current_page: number; + status: "reading" | "finished" | "want_to_read"; +} + +interface BookCardProps { + book: Book; + onPress: () => void; +} + +export default function BookCard({ book, onPress }: BookCardProps) { + const { colors } = useTheme(); + const styles = StyleSheet.create({ + bookCard: { + flex: 1, + backgroundColor: colors.surface, + borderRadius: ROUNDNESS.md, + overflow: "hidden", + marginBottom: SPACING.md, + }, + bookCover: { + height: 160, + backgroundColor: colors.surfaceVariant, + justifyContent: "center", + alignItems: "center", + position: "relative", + }, + resumeIndicator: { + position: "absolute", + top: 8, + right: 8, + backgroundColor: colors.primary, + width: 32, + height: 32, + borderRadius: 16, + justifyContent: "center", + alignItems: "center", + }, + progressOverlay: { + position: "absolute", + bottom: 8, + right: 8, + backgroundColor: "rgba(0,0,0,0.6)", + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + }, + progressText: { + color: "#fff", + fontSize: 12, + fontWeight: "600", + }, + bookTitle: { + fontSize: 13, + fontWeight: "600", + color: colors.onSurface, + paddingHorizontal: SPACING.sm, + paddingTop: SPACING.sm, + }, + bookAuthor: { + fontSize: 11, + color: colors.outline, + paddingHorizontal: SPACING.sm, + }, + progressBarBg: { + height: 3, + backgroundColor: colors.surfaceVariant, + marginHorizontal: SPACING.sm, + marginVertical: SPACING.sm, + borderRadius: 1.5, + overflow: "hidden", + }, + progressBarFill: { + height: "100%", + borderRadius: 1.5, + }, + }); + + const progress = + book.total_pages > 0 + ? Math.round((book.current_page / book.total_pages) * 100) + : 0; + + return ( + + + + {book.status === "reading" && ( + + + + )} + + {progress}% + + + + {book.title} + + + {book.author} + + + + + + + ); +} diff --git a/src/components/Library/BookDetailsModal.tsx b/src/components/Library/BookDetailsModal.tsx new file mode 100644 index 0000000..438f2cd --- /dev/null +++ b/src/components/Library/BookDetailsModal.tsx @@ -0,0 +1,391 @@ +import { ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { useData } from "@/src/hooks/useData"; +import { + Book as BookIcon, + Clock, + Play, + Sparkles, + Trash2, + TrendingUp, + X, + ChevronRight, + Calendar, +} from "lucide-react-native"; +import React, { useMemo } from "react"; +import { + ActivityIndicator, + Modal, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; + +interface Book { + id: string; + title: string; + author: string; + total_pages: number; + current_page: number; + status: "reading" | "finished" | "want_to_read"; + file_uri: string; + updated_at: string; +} + +interface ReadingLog { + id: string; + start_page: number; + end_page: number; + pages_read: number; + duration_seconds: number; + logged_at: string; +} + +interface BookDetailsModalProps { + visible: boolean; + onClose: () => void; + book: Book | null; + isGeneratingAI: boolean; + onContinueReading: () => void; + onGenerateAI: () => void; + onDelete: () => void; +} + +export default function BookDetailsModal({ + visible, + onClose, + book, + isGeneratingAI, + onContinueReading, + onGenerateAI, + onDelete, +}: BookDetailsModalProps) { + const { colors } = useTheme(); + const styles = useMemo(() => createStyles(colors), [colors]); + + const { data: logs, loading: logsLoading } = useData( + "SELECT * FROM reading_logs WHERE book_id = ? ORDER BY logged_at DESC LIMIT 5", + [book?.id || ""], + ); + + const formatDuration = (seconds: number) => { + const mins = Math.floor(seconds / 60); + if (mins < 1) return "< 1 min"; + return `${mins} min${mins > 1 ? "s" : ""}`; + }; + + const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + return date.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + }); + }; + + return ( + + + + + Book Details + + + + + + {book && ( + + + + + + + {book.title} + {book.author} + + + + + {book.current_page} / {book.total_pages} pages + + + + + + Last read:{" "} + {new Date(book.updated_at).toLocaleDateString()} + + + + + + + + + Continue Reading + + + {/* Reading History Section */} + + + Reading History + + + + {logsLoading ? ( + + ) : logs.length > 0 ? ( + + {logs.map((log) => ( + + + + + {formatDate(log.logged_at)} + + + p. {log.start_page} → {log.end_page} + + + + + {formatDuration(log.duration_seconds)} + + +{log.pages_read} pages + + + ))} + + ) : ( + No reading sessions logged yet. + )} + + + + + {isGeneratingAI ? ( + + ) : ( + + )} + + Generate AI Insights + + + + + + Remove from Library + + + + )} + + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.5)", + justifyContent: "flex-end", + }, + modalContainer: { + maxHeight: "90%", + backgroundColor: colors.surface, + borderTopLeftRadius: ROUNDNESS.lg, + borderTopRightRadius: ROUNDNESS.lg, + }, + modalHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderBottomWidth: 1, + borderBottomColor: colors.surfaceVariant, + }, + modalTitle: { + fontSize: 16, + fontWeight: "700", + color: colors.onSurface, + }, + detailsHeader: { + flexDirection: "row", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.lg, + gap: SPACING.lg, + }, + bookCover: { + backgroundColor: colors.surfaceVariant, + borderRadius: ROUNDNESS.md, + justifyContent: "center", + alignItems: "center", + }, + detailsMeta: { + flex: 1, + justifyContent: "flex-start", + }, + detailsTitle: { + fontSize: 14, + fontWeight: "700", + color: colors.onSurface, + marginBottom: SPACING.sm, + }, + detailsAuthor: { + fontSize: 12, + color: colors.outline, + marginBottom: SPACING.md, + }, + detailsStats: { + gap: SPACING.sm, + }, + detailStatItem: { + flexDirection: "row", + alignItems: "center", + gap: SPACING.sm, + }, + detailStatText: { + fontSize: 11, + color: colors.onSurface, + }, + primaryBtn: { + backgroundColor: colors.primary, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderRadius: ROUNDNESS.md, + gap: SPACING.sm, + marginHorizontal: SPACING.lg, + marginVertical: SPACING.md, + }, + primaryBtnText: { + color: colors.onPrimary, + fontSize: 14, + fontWeight: "600", + letterSpacing: 0.5, + }, + historySection: { + paddingHorizontal: SPACING.lg, + marginVertical: SPACING.md, + }, + sectionHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: SPACING.md, + }, + sectionTitle: { + fontSize: 14, + fontWeight: "700", + color: colors.onSurface, + letterSpacing: 0.5, + }, + logsList: { + gap: SPACING.sm, + }, + logItem: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: colors.surfaceVariant + "40", + padding: SPACING.md, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "20", + }, + logLeft: { + gap: 4, + }, + dateChip: { + flexDirection: "row", + alignItems: "center", + gap: 4, + backgroundColor: colors.primary + "15", + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 4, + alignSelf: "flex-start", + }, + logDateText: { + fontSize: 9, + fontWeight: "600", + color: colors.primary, + }, + logPagesText: { + fontSize: 12, + fontWeight: "600", + color: colors.onSurface, + }, + logRight: { + alignItems: "flex-end", + gap: 2, + }, + logDurationText: { + fontSize: 11, + fontWeight: "600", + color: colors.onSurface, + }, + logDeltaText: { + fontSize: 10, + color: colors.outline, + }, + emptyLogsText: { + fontSize: 12, + color: colors.outline, + fontStyle: "italic", + textAlign: "center", + marginVertical: SPACING.md, + }, + actionSection: { + paddingHorizontal: SPACING.lg, + marginTop: SPACING.lg, + gap: SPACING.md, + }, + deleteActionBtn: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + marginBottom: SPACING.lg, + gap: SPACING.sm, + borderWidth: 1, + borderColor: colors.error + "30", + borderRadius: ROUNDNESS.md, + }, + deleteActionText: { + color: colors.error, + fontSize: 14, + fontWeight: "600", + letterSpacing: 0.5, + }, + }); + diff --git a/src/components/Library/BookGrid.tsx b/src/components/Library/BookGrid.tsx new file mode 100644 index 0000000..b81d9e4 --- /dev/null +++ b/src/components/Library/BookGrid.tsx @@ -0,0 +1,90 @@ +import { SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { Upload } from "lucide-react-native"; +import React from "react"; +import { + ActivityIndicator, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; +import BookCard from "./BookCard"; + +interface Book { + id: string; + title: string; + author: string; + total_pages: number; + current_page: number; + status: "reading" | "finished" | "want_to_read"; + file_uri: string; + cover_uri: string; +} + +interface BookGridProps { + books: Book[]; + loading: boolean; + onBookPress: (book: Book) => void; + onEmptyPress: () => void; +} + +export default function BookGrid({ + books, + loading, + onBookPress, + onEmptyPress, +}: BookGridProps) { + const { colors } = useTheme(); + const styles = StyleSheet.create({ + bookGrid: { + paddingHorizontal: SPACING.lg, + gap: SPACING.md, + }, + emptyBookCard: { + flex: 1, + backgroundColor: colors.surfaceVariant + "40", + borderStyle: "dashed", + borderWidth: 2, + borderColor: colors.outline + "40", + borderRadius: 12, + padding: SPACING.lg, + alignItems: "center", + justifyContent: "center", + minHeight: 200, + gap: SPACING.md, + }, + emptyBookText: { + fontSize: 14, + fontWeight: "600", + color: colors.onSurface, + }, + emptyBookSub: { + fontSize: 12, + color: colors.outline, + }, + }); + + if (loading) { + return ( + + + + ); + } + + return ( + + {books.map((book) => ( + onBookPress(book)} /> + ))} + {books.length === 0 && ( + + + Your library is empty + Tap to add your first book + + )} + + ); +} diff --git a/src/components/Library/LibraryHeader.tsx b/src/components/Library/LibraryHeader.tsx new file mode 100644 index 0000000..9a82ec5 --- /dev/null +++ b/src/components/Library/LibraryHeader.tsx @@ -0,0 +1,74 @@ +import { SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { useRouter } from "expo-router"; +import { Menu, Settings } from "lucide-react-native"; +import React from "react"; +import { Image, StyleSheet, TouchableOpacity, View } from "react-native"; + +interface LibraryHeaderProps { + onSettingsPress?: () => void; + onMenuPress?: () => void; +} + +export default function LibraryHeader({ + onSettingsPress, + onMenuPress, +}: LibraryHeaderProps) { + const { colors } = useTheme(); + const router = useRouter(); + + const styles = StyleSheet.create({ + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + backgroundColor: colors.background, + height: 60, + }, + menuBtn: { + padding: 8, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + logoImage: { + height: 40, + width: 160, + }, + ghostBtn: { + padding: 8, + }, + }); + + return ( + + router.push("/menu"))} + > + + + + + + router.push("/modal"))} + > + + + + ); +} diff --git a/src/components/Library/LibraryStats.tsx b/src/components/Library/LibraryStats.tsx new file mode 100644 index 0000000..b48f08d --- /dev/null +++ b/src/components/Library/LibraryStats.tsx @@ -0,0 +1,67 @@ +import { SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { BookOpen, CheckCircle2, TrendingUp } from "lucide-react-native"; +import React, { useMemo } from "react"; +import { StyleSheet, Text, View } from "react-native"; + +interface LibraryStatsProps { + stats: { + reading: number; + finished: number; + pages: number; + }; +} + +export default function LibraryStats({ stats }: LibraryStatsProps) { + const { colors, isDark } = useTheme(); + const styles = useMemo(() => createStyles(colors, isDark), [colors, isDark]); + + return ( + + + + {stats.pages} + PAGES READ + + + + {stats.reading} + ACTIVE + + + + {stats.finished} + FINISHED + + + ); +} + +const createStyles = (colors: any, isDark: boolean) => + StyleSheet.create({ + statsSection: { + flexDirection: "row", + gap: SPACING.md, + marginVertical: SPACING.lg, + paddingHorizontal: SPACING.lg, + }, + statCard: { + flex: 1, + backgroundColor: colors.surfaceVariant, + borderRadius: 12, + padding: SPACING.md, + alignItems: "center", + gap: SPACING.sm, + }, + statValue: { + fontSize: 18, + fontWeight: "700", + color: colors.onSurface, + }, + statLabel: { + fontSize: 9, + fontWeight: "600", + color: colors.outline, + letterSpacing: 0.5, + }, + }); diff --git a/src/components/Library/NoteEditorModal.tsx b/src/components/Library/NoteEditorModal.tsx new file mode 100644 index 0000000..3817c90 --- /dev/null +++ b/src/components/Library/NoteEditorModal.tsx @@ -0,0 +1,138 @@ +import { ROUNDNESS, SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { Save, X } from "lucide-react-native"; +import React from "react"; +import { + KeyboardAvoidingView, + Modal, + Platform, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; + +interface NoteEditorModalProps { + visible: boolean; + onClose: () => void; + currentPage: number; + currentNote: string; + onNoteChange: (text: string) => void; + onSave: () => void; + isEditing: boolean; +} + +export default function NoteEditorModal({ + visible, + onClose, + currentPage, + currentNote, + onNoteChange, + onSave, + isEditing, +}: NoteEditorModalProps) { + const { colors } = useTheme(); + const styles = StyleSheet.create({ + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.5)", + justifyContent: "flex-end", + }, + modalContainer: { + maxHeight: "90%", + backgroundColor: colors.surface, + borderTopLeftRadius: ROUNDNESS.lg, + borderTopRightRadius: ROUNDNESS.lg, + }, + modalHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderBottomWidth: 1, + borderBottomColor: colors.surfaceVariant, + }, + modalTitle: { + fontSize: 16, + fontWeight: "700", + color: colors.onSurface, + }, + modalField: { + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + }, + noteInput: { + minHeight: 120, + textAlignVertical: "top", + paddingTop: SPACING.md, + }, + modalInput: { + borderWidth: 1, + borderColor: colors.surfaceVariant, + borderRadius: ROUNDNESS.md, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.md, + fontSize: 14, + color: colors.onSurface, + backgroundColor: colors.background, + }, + primaryBtn: { + backgroundColor: colors.primary, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderRadius: ROUNDNESS.md, + gap: SPACING.sm, + marginHorizontal: SPACING.lg, + marginBottom: SPACING.lg, + }, + primaryBtnText: { + color: colors.onPrimary, + fontSize: 14, + fontWeight: "600", + letterSpacing: 0.5, + }, + }); + + return ( + + + + + + {isEditing ? "Edit Note" : `Add Note - Page ${currentPage}`} + + + + + + + + + + + + {isEditing ? "Update Note" : "Save Note"} + + + + + + ); +} diff --git a/src/components/Library/NotesPanel.tsx b/src/components/Library/NotesPanel.tsx new file mode 100644 index 0000000..1bf0ca6 --- /dev/null +++ b/src/components/Library/NotesPanel.tsx @@ -0,0 +1,140 @@ +import { SPACING } from "@/src/constants/Theme"; +import { useTheme } from "@/src/hooks/useTheme"; +import { Pencil, Plus, Trash2 } from "lucide-react-native"; +import React, { useMemo } from "react"; +import { + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; +import Animated from "react-native-reanimated"; + +interface Note { + id: string; + page: number; + content: string; + timestamp: string; + color?: string; +} + +interface NotesPanelProps { + currentPage: number; + notes: Note[]; + animatedStyle: any; + onAddNote: () => void; + onEditNote: (note: Note) => void; + onDeleteNote: (noteId: string) => void; +} + +export default function NotesPanel({ + currentPage, + notes, + animatedStyle, + onAddNote, + onEditNote, + onDeleteNote, +}: NotesPanelProps) { + const { colors } = useTheme(); + const styles = useMemo(() => createStyles(colors), [colors]); + + const currentPageNotes = notes.filter((note) => note.page === currentPage); + + return ( + + + Notes for Page {currentPage} + + + + + + {currentPageNotes.map((note) => ( + + + {note.content} + + {new Date(note.timestamp).toLocaleTimeString()} + + + + onEditNote(note)}> + + + onDeleteNote(note.id)}> + + + + + ))} + {currentPageNotes.length === 0 && ( + No notes for this page + )} + + + ); +} + +const createStyles = (colors: any) => + StyleSheet.create({ + notesPanel: { + position: "absolute", + bottom: 0, + left: 0, + right: 0, + backgroundColor: colors.surface, + borderTopWidth: 1, + borderTopColor: colors.surfaceVariant, + zIndex: 50, + }, + notesHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderBottomWidth: 1, + borderBottomColor: colors.surfaceVariant, + }, + notesTitle: { + fontSize: 14, + fontWeight: "600", + color: colors.onSurface, + }, + notesList: { + maxHeight: 200, + }, + noteItem: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-start", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + borderBottomWidth: 1, + borderBottomColor: colors.surfaceVariant, + }, + noteContent: { + flex: 1, + marginRight: SPACING.md, + }, + noteText: { + fontSize: 13, + color: colors.onSurface, + marginBottom: SPACING.xs, + }, + noteTimestamp: { + fontSize: 10, + color: colors.outline, + }, + noteActions: { + flexDirection: "row", + gap: SPACING.sm, + }, + noNotesText: { + fontSize: 12, + color: colors.outline, + textAlign: "center", + paddingVertical: SPACING.lg, + }, + }); diff --git a/src/components/Library/PdfReader.tsx b/src/components/Library/PdfReader.tsx new file mode 100644 index 0000000..1745bd6 --- /dev/null +++ b/src/components/Library/PdfReader.tsx @@ -0,0 +1,464 @@ +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { + StyleSheet, + View, + Text, + TouchableOpacity, + Dimensions, + ActivityIndicator, + TextInput, + Platform, +} from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + FadeIn, + FadeOut, +} from 'react-native-reanimated'; +import { BlurView } from 'expo-blur'; +import { + X, + ChevronLeft, + Settings, + BookOpen, + MessageSquare, + Clock, + Play, + Pause, + ArrowRight +} from 'lucide-react-native'; +import { ROUNDNESS, SPACING } from '@/src/constants/Theme'; +import { useTheme } from '@/src/hooks/useTheme'; + +import Constants, { ExecutionEnvironment } from 'expo-constants'; + +import { WebView } from 'react-native-webview'; +import PDFReader from '@bildau/rn-pdf-reader'; +import * as FileSystem from 'expo-file-system/legacy'; + +// Safe PDF Component Wrapper +const PdfRendererComponent = React.memo((props: any) => { + const { source, style, onLoad, onPageChange, page } = props; + const [base64Source, setBase64Source] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + let isMounted = true; + + const loadSource = async () => { + if (Platform.OS === 'android' && source.startsWith('file://')) { + try { + console.log('[PdfReader] Android local file detected, reading as base64...'); + const base64 = await FileSystem.readAsStringAsync(source, { + encoding: FileSystem.EncodingType.Base64, + }); + if (isMounted) { + setBase64Source(`data:application/pdf;base64,${base64}`); + } + } catch (e: any) { + console.error('[PdfReader] Failed to read local file as base64:', e); + if (isMounted) { + setError(e.message); + } + } + } else { + if (isMounted) { + setBase64Source(null); + } + } + }; + + loadSource(); + return () => { isMounted = false; }; + }, [source]); + + // Memoize source object to avoid triggering PDFReader's componentDidUpdate unnecessarily + const memoizedSource = useMemo(() => { + if (base64Source) { + return { base64: base64Source }; + } + return { uri: source }; + }, [source, base64Source]); + + if (Platform.OS === 'android' && source.startsWith('file://') && !base64Source && !error) { + return ( + + + + ); + } + + return ( + { + // The library doesn't easily expose total pages here, + // but we can signal it's loaded. + onLoad?.(0); + }} + webviewProps={{ + onMessage: (event: any) => { + try { + const data = JSON.parse(event.nativeEvent.data); + if (data.type === 'pageChange') { + onPageChange?.(data.page - 1, data.total); + } + } catch (e) {} + } + }} + /> + ); +}); + +const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window'); + +interface PdfReaderProps { + uri: string; + title: string; + initialPage?: number; + onClose: () => void; + onPageChange?: (page: number, total: number) => void; + onSessionUpdate?: (seconds: number) => void; + onAddNote?: (page: number) => void; +} + +export const PdfReader: React.FC = ({ + uri, + title, + initialPage = 0, + onClose, + onPageChange, + onSessionUpdate, + onAddNote, +}) => { + const { colors, isDark } = useTheme(); + const insets = useSafeAreaInsets(); + + // State + const [currentPage, setCurrentPage] = useState(initialPage); + const [totalPages, setTotalPages] = useState(0); + const [controlsVisible, setControlsVisible] = useState(true); + const [sessionSeconds, setSessionSeconds] = useState(0); + const [isTimerRunning, setIsTimerRunning] = useState(true); + const [jumpToPage, setJumpToPage] = useState(''); + const [isLoading, setIsLoading] = useState(true); + + // Animations + const controlsOpacity = useSharedValue(1); + const topBarY = useSharedValue(0); + const bottomBarY = useSharedValue(0); + + // Timer logic + useEffect(() => { + let interval: NodeJS.Timeout | null = null; + if (isTimerRunning) { + interval = setInterval(() => { + setSessionSeconds(s => s + 1); + }, 1000); + } + return () => { + if (interval) clearInterval(interval); + }; + }, [isTimerRunning]); + + // Report session time to parent safely + useEffect(() => { + onSessionUpdate?.(sessionSeconds); + }, [sessionSeconds]); + + // Auto-hide controls + useEffect(() => { + let timer: NodeJS.Timeout; + if (controlsVisible) { + timer = setTimeout(() => { + toggleControls(false); + }, 5000); + } + return () => clearTimeout(timer); + }, [controlsVisible]); + + const toggleControls = useCallback((force?: boolean) => { + const nextVisible = force !== undefined ? force : !controlsVisible; + setControlsVisible(nextVisible); + + const targetOpacity = nextVisible ? 1 : 0; + const targetY = nextVisible ? 0 : -100; + const targetBottomY = nextVisible ? 0 : 100; + + controlsOpacity.value = withTiming(targetOpacity); + topBarY.value = withTiming(targetY); + bottomBarY.value = withTiming(targetBottomY); + }, [controlsVisible]); + + const animatedTopBarStyle = useAnimatedStyle(() => ({ + opacity: controlsOpacity.value, + transform: [{ translateY: topBarY.value }], + })); + + const animatedBottomBarStyle = useAnimatedStyle(() => ({ + opacity: controlsOpacity.value, + transform: [{ translateY: bottomBarY.value }], + })); + + const formatTime = (totalSeconds: number) => { + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${minutes}:${seconds.toString().padStart(2, '0')}`; + }; + + const handlePageChange = useCallback((page: number, total: number) => { + setCurrentPage(page); + setTotalPages(total); + // Use a small delay to avoid "update while rendering" if called synchronously by PdfRenderer + setTimeout(() => { + onPageChange?.(page, total); + }, 0); + }, [onPageChange]); + + const handleLoad = useCallback((total: number) => { + setTotalPages(total); + setIsLoading(false); + }, []); + + const handleGoToPage = () => { + const page = parseInt(jumpToPage); + if (!isNaN(page) && page >= 1 && page <= totalPages) { + // In react-native-pdf-renderer, we might need a ref to the component to scroll + // For now, we update the state and assume the component reacts to it or we'll add a ref later + setCurrentPage(page - 1); + setJumpToPage(''); + } + }; + + return ( + + {/* PDF View */} + toggleControls()} + style={styles.pdfContainer} + > + + + {isLoading && ( + + + + )} + + + {/* Top Controls */} + + + + + + + + + + {title} + + + {currentPage + 1} / {totalPages} + + + + + + + {formatTime(sessionSeconds)} + + + + onAddNote?.(currentPage)} + style={styles.iconButton} + > + + + + + + + {/* Bottom Controls */} + + + + + + + + + + + setIsTimerRunning(!isTimerRunning)} + style={[styles.sessionButton, { backgroundColor: isTimerRunning ? colors.secondaryContainer : colors.primary }]} + > + {isTimerRunning ? ( + <> + + Pause Session + + ) : ( + <> + + Resume Session + + )} + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + pdfContainer: { + flex: 1, + }, + pdf: { + flex: 1, + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + }, + loadingOverlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'center', + alignItems: 'center', + }, + topBarContainer: { + position: 'absolute', + left: SPACING.md, + right: SPACING.md, + zIndex: 1000, + }, + bottomBarContainer: { + position: 'absolute', + left: SPACING.md, + right: SPACING.md, + zIndex: 1000, + }, + glassBar: { + borderRadius: ROUNDNESS.full, + overflow: 'hidden', + padding: SPACING.sm, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.1)', + }, + topBarContent: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: SPACING.sm, + }, + titleContainer: { + flex: 1, + marginHorizontal: SPACING.md, + alignItems: 'center', + }, + bookTitle: { + fontSize: 14, + fontWeight: '700', + textAlign: 'center', + }, + pageText: { + fontSize: 12, + color: 'rgba(128,128,128,0.8)', + marginTop: 2, + }, + iconButton: { + width: 40, + height: 40, + borderRadius: 20, + justifyContent: 'center', + alignItems: 'center', + }, + timerChip: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255,255,255,0.1)', + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 12, + marginRight: SPACING.sm, + }, + timerText: { + fontSize: 12, + fontWeight: '600', + marginLeft: 4, + }, + bottomBarContent: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: SPACING.sm, + }, + jumpContainer: { + flexDirection: 'row', + alignItems: 'center', + flex: 1, + marginRight: SPACING.md, + }, + pageInput: { + width: 60, + height: 40, + borderRadius: 20, + borderWidth: 1, + paddingHorizontal: 12, + fontSize: 14, + backgroundColor: 'rgba(255,255,255,0.05)', + }, + goButton: { + width: 36, + height: 36, + borderRadius: 18, + justifyContent: 'center', + alignItems: 'center', + marginLeft: 8, + }, + sessionButton: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 25, + }, + sessionButtonText: { + fontSize: 14, + fontWeight: '700', + marginLeft: 8, + }, +}); + +export default PdfReader; diff --git a/src/components/animated-splash-screen.tsx b/src/components/animated-splash-screen.tsx new file mode 100644 index 0000000..00dec2c --- /dev/null +++ b/src/components/animated-splash-screen.tsx @@ -0,0 +1,125 @@ +import React, { useEffect } from 'react'; +import { StyleSheet, View, Image, Dimensions, Text } from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + withSequence, + withDelay, + runOnJS, + Easing +} from 'react-native-reanimated'; + +const { width, height } = Dimensions.get('window'); + +interface Props { + onAnimationFinish: () => void; + backgroundColor: string; +} + +export function AnimatedSplashScreen({ onAnimationFinish, backgroundColor }: Props) { + const scale = useSharedValue(0.3); + const opacity = useSharedValue(0); + const footerOpacity = useSharedValue(0); + const containerOpacity = useSharedValue(1); + + const animatedLogoStyle = useAnimatedStyle(() => { + 'worklet'; + return { + transform: [{ scale: scale.value }], + opacity: opacity.value, + }; + }); + + const animatedFooterStyle = useAnimatedStyle(() => { + 'worklet'; + return { + opacity: footerOpacity.value, + transform: [{ translateY: withTiming(footerOpacity.value === 1 ? 0 : 20, { duration: 800 }) }], + }; + }); + + const animatedContainerStyle = useAnimatedStyle(() => { + 'worklet'; + return { + opacity: containerOpacity.value, + }; + }); + + useEffect(() => { + // Sequence: Fade in and scale up -> Hold -> Zoom in/Fade out container + scale.value = withTiming(1, { + duration: 1000, + easing: Easing.out(Easing.back(1.5)) + }); + opacity.value = withTiming(1, { duration: 800 }); + + // Fade in footer slightly later + footerOpacity.value = withDelay(400, withTiming(1, { duration: 800 })); + + containerOpacity.value = withDelay( + 2500, + withTiming(0, { duration: 500 }, (finished) => { + if (finished) { + runOnJS(onAnimationFinish)(); + } + }) + ); + }, []); + + return ( + + + + + + + powered by + + + + ); +} + +const styles = StyleSheet.create({ + container: { + ...StyleSheet.absoluteFillObject, + alignItems: 'center', + justifyContent: 'center', + zIndex: 9999, + }, + content: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + logo: { + width: width * 0.45, + height: width * 0.45, + }, + footer: { + position: 'absolute', + bottom: 50, + alignItems: 'center', + gap: 8, + }, + poweredBy: { + fontSize: 12, + color: '#94a3b8', + textTransform: 'lowercase', + letterSpacing: 1, + fontFamily: 'Manrope_400Regular', + }, + footerLogo: { + width: width * 0.3, + height: 40, + }, +}); diff --git a/src/components/chat/ChatHeader.tsx b/src/components/chat/ChatHeader.tsx new file mode 100644 index 0000000..6378eac --- /dev/null +++ b/src/components/chat/ChatHeader.tsx @@ -0,0 +1,77 @@ +import { SPACING, ThemeColors } from "@/src/constants/Theme"; +import { Menu, Settings } from "lucide-react-native"; +import React, { useMemo } from "react"; +import { Image, StyleSheet, TouchableOpacity, View } from "react-native"; + +interface ChatHeaderProps { + colors: ThemeColors; + onMenuPress: () => void; + onSettingsPress: () => void; +} + +const ChatHeader = ({ + colors, + onMenuPress, + onSettingsPress, +}: ChatHeaderProps) => { + const styles = useMemo(() => createStyles(colors), [colors]); + + return ( + + + + + + + + + + + + + + ); +}; + +const createStyles = (_colors: ThemeColors) => + StyleSheet.create({ + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.md, + height: 60, + }, + iconButton: { + padding: 8, + }, + logoContainer: { + position: "absolute", + left: 0, + right: 0, + alignItems: "center", + justifyContent: "center", + zIndex: -1, + }, + logoImage: { + height: 40, + width: 160, + }, + }); + +export default React.memo(ChatHeader); diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx new file mode 100644 index 0000000..8516513 --- /dev/null +++ b/src/components/chat/ChatInput.tsx @@ -0,0 +1,106 @@ +import { FONTS, ROUNDNESS, SPACING, ThemeColors } from "@/src/constants/Theme"; +import { Send } from "lucide-react-native"; +import React, { useMemo } from "react"; +import { + Platform, + StyleSheet, + TextInput, + TouchableOpacity, + View, +} from "react-native"; + +interface ChatInputProps { + inputText: string; + onChangeText: (text: string) => void; + onSend: (text: string) => void; + placeholder: string; + colors: ThemeColors; + disabled?: boolean; +} + +const ChatInput = ({ + inputText, + onChangeText, + onSend, + placeholder, + colors, + disabled = false, +}: ChatInputProps) => { + const styles = useMemo(() => createStyles(colors), [colors]); + + const handleSend = () => { + if (inputText.trim() && !disabled) { + onSend(inputText); + } + }; + + return ( + + + + + + + + + ); +}; + +const createStyles = (colors: ThemeColors) => + StyleSheet.create({ + inputSection: { + backgroundColor: colors.surface, + paddingBottom: Platform.OS === "ios" ? 10 : 20, + }, + inputBarContainer: { + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.surface, + marginHorizontal: SPACING.lg, + borderRadius: ROUNDNESS.xl, + borderWidth: 1, + borderColor: colors.outlineVariant + "80", + padding: 4, + marginBottom: 10, + }, + input: { + flex: 1, + fontFamily: FONTS.body, + fontSize: 15, + paddingHorizontal: 16, + height: 48, + color: colors.onSurface, + }, + sendButton: { + width: 40, + height: 40, + borderRadius: 20, + alignItems: "center", + justifyContent: "center", + }, + }); + +export default React.memo(ChatInput); diff --git a/src/components/chat/MessageBubble.tsx b/src/components/chat/MessageBubble.tsx new file mode 100644 index 0000000..8f4ec7e --- /dev/null +++ b/src/components/chat/MessageBubble.tsx @@ -0,0 +1,155 @@ +import TypingIndicator from "@/src/components/chat/TypingIndicator"; +import { FONTS, ROUNDNESS, SPACING, ThemeColors } from "@/src/constants/Theme"; +import { ChatMessage } from "@/src/types/chat"; +import { formatTime } from "@/src/utils/formatTime"; +import { CheckCircle2 } from "lucide-react-native"; +import React, { useMemo } from "react"; +import { StyleSheet, Text, View } from "react-native"; + +interface MessageBubbleProps { + message: ChatMessage; + isUser: boolean; + colors: ThemeColors; +} + +const MessageBubble = ({ message, isUser, colors }: MessageBubbleProps) => { + const styles = useMemo(() => createStyles(colors), [colors]); + const actionLabel = message.action?.replace(/_/g, " "); + const isSending = message.status === "sending"; + const hasError = message.status === "error"; + + return ( + + + {isSending ? ( + + ) : ( + + {message.content} + + )} + + {actionLabel ? ( + + + + {actionLabel} + + + ) : null} + + {hasError ? ( + + Unable to generate response. Tap send again. + + ) : null} + + + {formatTime(message.createdAt)} + + ); +}; + +const createStyles = (colors: ThemeColors) => + StyleSheet.create({ + messageWrapper: { + marginBottom: SPACING.lg, + maxWidth: "85%", + }, + aiMessageWrapper: { + alignSelf: "flex-start", + }, + userMessageWrapper: { + alignSelf: "flex-end", + alignItems: "flex-end", + }, + messageBubble: { + padding: 14, + borderRadius: ROUNDNESS.lg, + }, + aiBubble: { + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + borderTopLeftRadius: 4, + }, + userBubble: { + backgroundColor: colors.primary, + borderTopRightRadius: 4, + }, + messageText: { + fontFamily: FONTS.body, + fontSize: 15, + lineHeight: 22, + }, + aiText: { + color: colors.onSurface, + }, + userText: { + color: colors.onPrimary, + }, + actionBadge: { + flexDirection: "row", + alignItems: "center", + gap: 4, + marginTop: 8, + backgroundColor: "rgba(0,0,0,0.05)", + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + alignSelf: "flex-start", + }, + actionText: { + fontFamily: FONTS.label, + fontSize: 9, + letterSpacing: 0.5, + textTransform: "uppercase", + }, + messageTime: { + fontFamily: FONTS.label, + fontSize: 9, + color: colors.onSurfaceVariant, + marginTop: 6, + }, + errorText: { + marginTop: 10, + fontFamily: FONTS.label, + fontSize: 11, + color: colors.error, + }, + }); + +const areEqual = (prev: MessageBubbleProps, next: MessageBubbleProps) => + prev.message.id === next.message.id && + prev.message.content === next.message.content && + prev.message.status === next.message.status && + prev.message.action === next.message.action && + prev.isUser === next.isUser && + prev.colors.primary === next.colors.primary && + prev.colors.surface === next.colors.surface && + prev.colors.onSurface === next.colors.onSurface; + +export default React.memo(MessageBubble, areEqual); diff --git a/src/components/chat/SuggestionChips.tsx b/src/components/chat/SuggestionChips.tsx new file mode 100644 index 0000000..84d64e6 --- /dev/null +++ b/src/components/chat/SuggestionChips.tsx @@ -0,0 +1,87 @@ +import { FONTS, ROUNDNESS, SPACING, ThemeColors } from "@/src/constants/Theme"; +import React, { useCallback } from "react"; +import { + FlatList, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; + +const suggestions = [ + "Audit my habits", + "Suggest a 2-min version", + "Log my workout", + "Help me build a morning routine", +] as const; + +interface SuggestionChipsProps { + colors: ThemeColors; + onSuggestionPress: (message: string) => void; + disabled?: boolean; +} + +const SuggestionChips = ({ + colors, + onSuggestionPress, + disabled = false, +}: SuggestionChipsProps) => { + const styles = createStyles(colors); + + const renderSuggestion = useCallback( + ({ item }: { item: string }) => ( + !disabled && onSuggestionPress(item)} + disabled={disabled} + accessibilityRole="button" + accessibilityLabel={`Send suggestion: ${item}`} + > + {item} + + ), + [onSuggestionPress, styles, disabled], + ); + + return ( + + item} + renderItem={renderSuggestion} + showsHorizontalScrollIndicator={false} + contentContainerStyle={styles.content} + ItemSeparatorComponent={() => } + /> + + ); +}; + +const createStyles = (colors: ThemeColors) => + StyleSheet.create({ + container: { + paddingVertical: SPACING.md, + }, + content: { + paddingHorizontal: SPACING.lg, + }, + suggestionSpacer: { + width: SPACING.sm, + }, + suggestionButton: { + backgroundColor: colors.surfaceVariant + "80", + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: ROUNDNESS.md, + borderWidth: 1, + borderColor: colors.outlineVariant + "33", + }, + suggestionText: { + fontFamily: FONTS.labelSm, + fontSize: 11, + color: colors.onSurface, + }, + }); + +export default React.memo(SuggestionChips); diff --git a/src/components/chat/TypingIndicator.tsx b/src/components/chat/TypingIndicator.tsx new file mode 100644 index 0000000..2bf21a8 --- /dev/null +++ b/src/components/chat/TypingIndicator.tsx @@ -0,0 +1,48 @@ +import { FONTS, ROUNDNESS, ThemeColors } from "@/src/constants/Theme"; +import React, { useEffect, useMemo, useState } from "react"; +import { StyleSheet, Text, View } from "react-native"; + +interface TypingIndicatorProps { + colors: ThemeColors; +} + +const TypingIndicator = ({ colors }: TypingIndicatorProps) => { + const [dots, setDots] = useState(1); + const styles = useMemo(() => createStyles(colors), [colors]); + + useEffect(() => { + const interval = setInterval(() => { + setDots((value) => (value % 3) + 1); + }, 420); + + return () => clearInterval(interval); + }, []); + + return ( + + {`Architecting response${".".repeat(dots)}`} + + ); +}; + +const createStyles = (colors: ThemeColors) => + StyleSheet.create({ + wrapper: { + backgroundColor: colors.surface, + paddingVertical: 10, + paddingHorizontal: 12, + borderRadius: ROUNDNESS.lg, + borderWidth: 1, + borderColor: colors.outlineVariant + "4D", + }, + text: { + fontFamily: FONTS.body, + fontSize: 15, + lineHeight: 22, + color: colors.onSurface, + }, + }); + +export default React.memo(TypingIndicator); diff --git a/src/components/ui/CalendarStrip.tsx b/src/components/ui/CalendarStrip.tsx new file mode 100644 index 0000000..c3c929a --- /dev/null +++ b/src/components/ui/CalendarStrip.tsx @@ -0,0 +1,390 @@ +import React, { useMemo, useRef, useState, useEffect } from 'react'; +import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, Platform, FlatList } from 'react-native'; +import { FONTS, SPACING, ROUNDNESS } from '@/src/constants/Theme'; +import { getLocalDateString } from '@/src/lib/date-utils'; +import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, X, Check } from 'lucide-react-native'; +import * as Haptics from 'expo-haptics'; + +interface CalendarStripProps { + selectedDate: string; // YYYY-MM-DD + onDateSelect: (date: string) => void; + colors: any; +} + +export const CalendarStrip: React.FC = ({ selectedDate, onDateSelect, colors }) => { + const scrollRef = useRef(null); + const [showPicker, setShowPicker] = useState(false); + + // Constants for scroll calculation + const ITEM_WIDTH = 55; + const GAP = 12; + + // Scroll to selected date on mount and when selectedDate changes + useEffect(() => { + // We want the selected item (index 7 in our 15-day range) to be centered + // Since we generate days centered around selectedDate, it's always at index 7 + const timer = setTimeout(() => { + if (scrollRef.current) { + // Approximate centering logic + // Each item + gap is ~67px. 7 items before = 469px. + // We want to scroll so the 8th item is in the middle of the screen. + scrollRef.current.scrollTo({ + x: (7 * (ITEM_WIDTH + GAP)) - 100, // Offset to bring it toward center + animated: true + }); + } + }, 100); + return () => clearTimeout(timer); + }, [selectedDate]); + + // Generate days centered around selectedDate + const days = useMemo(() => { + const result = []; + const centerDate = new Date(selectedDate + 'T00:00:00'); // Ensure local time + if (isNaN(centerDate.getTime())) return []; + + const start = new Date(centerDate); + start.setDate(centerDate.getDate() - 7); + + for (let i = 0; i < 15; i++) { + const d = new Date(start); + d.setDate(start.getDate() + i); + const dateStr = getLocalDateString(d); + result.push({ + date: dateStr, + dayName: d.toLocaleDateString('en-US', { weekday: 'short' }), + dayNum: d.getDate().toString(), + isToday: dateStr === getLocalDateString(new Date()), + }); + } + return result; + }, [selectedDate]); + + const handleDatePress = (date: string) => { + onDateSelect(date); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const openPicker = () => { + setShowPicker(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + }; + + return ( + + + + + + + + {days.map((day) => { + const isSelected = day.date === selectedDate; + + return ( + handleDatePress(day.date)} + activeOpacity={0.8} + style={[ + styles.dayCell, + isSelected && { backgroundColor: colors.primary } + ]} + > + + {day.dayName.toUpperCase()} + + + + + {day.dayNum} + + + + {day.isToday && !isSelected && ( + + )} + + ); + })} + + + + setShowPicker(false)} + selectedDate={selectedDate} + onSelect={(date) => { + onDateSelect(date); + setShowPicker(false); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + }} + colors={colors} + /> + + ); +}; + +const DatePickerModal = ({ visible, onClose, selectedDate, onSelect, colors }: any) => { + const [viewDate, setViewDate] = useState(new Date(selectedDate + 'T00:00:00')); + + useEffect(() => { + if (visible) { + setViewDate(new Date(selectedDate + 'T00:00:00')); + } + }, [visible, selectedDate]); + + const monthName = viewDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); + + const daysInMonth = useMemo(() => { + const year = viewDate.getFullYear(); + const month = viewDate.getMonth(); + const firstDay = new Date(year, month, 1).getDay(); + const lastDate = new Date(year, month + 1, 0).getDate(); + + const days = []; + // Padding for start of month + for (let i = 0; i < firstDay; i++) { + days.push(null); + } + // Days of month + for (let i = 1; i <= lastDate; i++) { + days.push(new Date(year, month, i)); + } + return days; + }, [viewDate]); + + const changeMonth = (offset: number) => { + const next = new Date(viewDate); + next.setMonth(viewDate.getMonth() + offset); + setViewDate(next); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + return ( + + + + + Select Date + + + + + + + changeMonth(-1)} style={styles.navIconBtn}> + + + {monthName} + changeMonth(1)} style={styles.navIconBtn}> + + + + + + {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((d, i) => ( + {d} + ))} + + + + {daysInMonth.map((date, i) => { + if (!date) return ; + + const dateStr = getLocalDateString(date); + const isSelected = dateStr === selectedDate; + const isToday = dateStr === getLocalDateString(new Date()); + + return ( + onSelect(dateStr)} + > + + {date.getDate()} + + + ); + })} + + + onSelect(getLocalDateString(new Date()))} + > + GO TO TODAY + + + + + ); +}; + +const styles = StyleSheet.create({ + outerContainer: { + paddingVertical: SPACING.md, + backgroundColor: 'transparent', + }, + stripWithButton: { + flexDirection: 'row', + alignItems: 'center', + paddingLeft: SPACING.lg, + }, + pickerBtn: { + width: 44, + height: 44, + borderRadius: 22, + alignItems: 'center', + justifyContent: 'center', + marginRight: 12, + borderWidth: 1, + }, + container: { + paddingRight: SPACING.lg, + gap: 12, + alignItems: 'center', + }, + dayCell: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 12, + paddingHorizontal: 10, + borderRadius: 30, + minWidth: 55, + }, + dayName: { + fontFamily: FONTS.label, + fontSize: 10, + marginBottom: 8, + letterSpacing: 0.5, + }, + dayNumContainer: { + width: 32, + height: 32, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 16, + }, + dayNum: { + fontFamily: FONTS.headline, + fontSize: 15, + }, + todayIndicator: { + position: 'absolute', + bottom: 6, + width: 4, + height: 4, + borderRadius: 2, + }, + // Modal Styles + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'center', + padding: 24, + }, + modalContent: { + borderRadius: ROUNDNESS.xl, + padding: SPACING.lg, + elevation: 5, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: SPACING.lg, + }, + modalTitle: { + fontFamily: FONTS.headline, + fontSize: 20, + }, + closeBtn: { + padding: 4, + }, + monthNav: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: SPACING.lg, + backgroundColor: 'rgba(0,0,0,0.03)', + borderRadius: ROUNDNESS.md, + padding: 4, + }, + navIconBtn: { + padding: 10, + }, + monthLabel: { + fontFamily: FONTS.labelSm, + fontSize: 16, + textTransform: 'uppercase', + letterSpacing: 1, + }, + weekDaysRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 12, + }, + weekDayText: { + width: '14.28%', + textAlign: 'center', + fontFamily: FONTS.label, + fontSize: 12, + }, + calendarGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + marginBottom: SPACING.xl, + }, + calendarDayCell: { + width: '14.28%', + height: 40, + alignItems: 'center', + justifyContent: 'center', + }, + calendarDayText: { + fontFamily: FONTS.body, + fontSize: 15, + }, + todayBtn: { + borderWidth: 1, + paddingVertical: 12, + borderRadius: ROUNDNESS.md, + alignItems: 'center', + justifyContent: 'center', + }, + todayBtnText: { + fontFamily: FONTS.labelSm, + fontSize: 13, + letterSpacing: 1, + } +}); diff --git a/src/components/ui/TimeInput.tsx b/src/components/ui/TimeInput.tsx new file mode 100644 index 0000000..1b279bf --- /dev/null +++ b/src/components/ui/TimeInput.tsx @@ -0,0 +1,285 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TextInput, + StyleSheet, + TouchableOpacity, + Modal, + FlatList, + Platform +} from 'react-native'; +import { Clock, X, Check } from 'lucide-react-native'; +import { FONTS, ROUNDNESS, SPACING } from '@/src/constants/Theme'; +import { useTheme } from '@/src/hooks/useTheme'; +import * as Haptics from 'expo-haptics'; + +interface TimeInputProps { + value: string; + onChange: (time: string) => void; + label?: string; +} + +export const TimeInput: React.FC = ({ value, onChange, label }) => { + const { colors } = useTheme(); + const [tempValue, setTempValue] = useState(value); + const [showPicker, setShowPicker] = useState(false); + + useEffect(() => { + setTempValue(value); + }, [value]); + + const formatTime = (text: string) => { + // Remove non-digits + const cleaned = text.replace(/\D/g, ''); + + if (cleaned.length === 0) return ''; + + let hours = '00'; + let minutes = '00'; + + if (cleaned.length === 1) { + hours = cleaned.padStart(2, '0'); + } else if (cleaned.length === 2) { + hours = cleaned; + } else if (cleaned.length === 3) { + hours = cleaned.slice(0, 1).padStart(2, '0'); + minutes = cleaned.slice(1); + } else { + hours = cleaned.slice(0, 2); + minutes = cleaned.slice(2, 4); + } + + let h = parseInt(hours); + let m = parseInt(minutes); + + if (isNaN(h)) h = 0; + if (isNaN(m)) m = 0; + + if (h > 23) h = 23; + if (m > 59) m = 59; + + return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; + }; + + const handleBlur = () => { + const formatted = formatTime(tempValue); + if (formatted) { + setTempValue(formatted); + onChange(formatted); + } else { + setTempValue(value); + } + }; + + // Dial Picker Logic + const HOURS = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')); + const MINUTES = Array.from({ length: 12 }, (_, i) => String(i * 5).padStart(2, '0')); + + const [selectedHour, setSelectedHour] = useState(value.split(':')[0] || '08'); + const [selectedMinute, setSelectedMinute] = useState(value.split(':')[1] || '00'); + + const openPicker = () => { + setSelectedHour(value.split(':')[0] || '08'); + setSelectedMinute(value.split(':')[1] || '00'); + setShowPicker(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }; + + const confirmPicker = () => { + const newTime = `${selectedHour}:${selectedMinute}`; + onChange(newTime); + setTempValue(newTime); + setShowPicker(false); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + }; + + return ( + + {label && {label}} + + + + + + + + + + + + Select Time + setShowPicker(false)}> + + + + + + + HOUR + item} + showsVerticalScrollIndicator={false} + snapToInterval={40} + renderItem={({ item }) => ( + { + setSelectedHour(item); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + {item} + + + )} + style={styles.dialList} + /> + + + : + + + MIN + item} + showsVerticalScrollIndicator={false} + snapToInterval={40} + renderItem={({ item }) => ( + { + setSelectedMinute(item); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} + > + + {item} + + + )} + style={styles.dialList} + /> + + + + + + Confirm Time + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + gap: 8, + }, + label: { + fontFamily: FONTS.labelSm, + fontSize: 11, + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: ROUNDNESS.md, + borderWidth: 1, + paddingHorizontal: 12, + }, + inputIcon: { + marginRight: 10, + }, + input: { + flex: 1, + height: 52, + fontFamily: FONTS.body, + fontSize: 16, + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'flex-end', + }, + modalContent: { + borderTopLeftRadius: ROUNDNESS.xl, + borderTopRightRadius: ROUNDNESS.xl, + padding: SPACING.lg, + paddingBottom: Platform.OS === 'ios' ? 40 : 20, + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: SPACING.xl, + }, + modalTitle: { + fontFamily: FONTS.headline, + fontSize: 18, + }, + pickerContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 20, + height: 200, + marginBottom: SPACING.xl, + }, + dialWrapper: { + alignItems: 'center', + width: 80, + }, + dialLabel: { + fontFamily: FONTS.label, + fontSize: 10, + marginBottom: 8, + }, + dialList: { + height: 160, + width: '100%', + }, + dialItem: { + height: 40, + alignItems: 'center', + justifyContent: 'center', + borderRadius: ROUNDNESS.sm, + }, + dialText: { + fontFamily: FONTS.labelSm, + fontSize: 20, + }, + separator: { + fontFamily: FONTS.headline, + fontSize: 24, + marginTop: 20, + }, + confirmBtn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + height: 56, + borderRadius: ROUNDNESS.full, + }, + confirmBtnText: { + fontFamily: FONTS.labelSm, + fontSize: 16, + }, +}); diff --git a/src/constants/Theme.ts b/src/constants/Theme.ts new file mode 100644 index 0000000..21abec4 --- /dev/null +++ b/src/constants/Theme.ts @@ -0,0 +1,132 @@ +export const ACCENTS = { + slate: { + label: "Modern Slate", + primary: "#334155", + primaryContainer: "#f1f5f9", + onPrimaryContainer: "#0f172a", + }, + blue: { + label: "Ocean Blue", + primary: "#4e607b", + primaryContainer: "#d3e3ff", + onPrimaryContainer: "#40536d", + }, + pink: { + label: "Rose Pink", + primary: "#a8385a", + primaryContainer: "#ffd9df", + onPrimaryContainer: "#3e001a", + }, + red: { + label: "Crimson Rust", + primary: "#a84c36", + primaryContainer: "#ffdad2", + onPrimaryContainer: "#3e0a01", + }, + purple: { + label: "Royal Amethyst", + primary: "#7c4dff", + primaryContainer: "#e0e0ff", + onPrimaryContainer: "#2c0091", + }, + orange: { + label: "Deep Amber", + primary: "#f57c00", + primaryContainer: "#fff3e0", + onPrimaryContainer: "#e65100", + }, + teal: { + label: "Deep Teal", + primary: "#00796b", + primaryContainer: "#e0f2f1", + onPrimaryContainer: "#004d40", + }, + emerald: { + label: "Vibrant Emerald", + primary: "#2e7d32", + primaryContainer: "#e8f5e9", + onPrimaryContainer: "#1b5e20", + }, +}; + +export type AccentKey = keyof typeof ACCENTS; + +export const COLORS = { + light: { + primary: "#334155", // Slate + onPrimary: "#f8faf9", + primaryContainer: "#f1f5f9", + onPrimaryContainer: "#0f172a", + secondary: "#4e607b", // Soft Blue + onSecondary: "#f8f8ff", + secondaryContainer: "#d3e3ff", + onSecondaryContainer: "#40536d", + tertiary: "#655b6f", // Muted Lavender + onTertiary: "#fef6ff", + background: "#f8faf9", // Off-white + onBackground: "#2d3433", + surface: "#f8faf9", + onSurface: "#2d3433", + surfaceVariant: "#eaefee", + onSurfaceVariant: "#596060", + outline: "#757c7b", + outlineVariant: "#acb3b2", + error: "#a83836", + onError: "#fff7f6", + }, + dark: { + primary: "#94a3b8", // Light Slate + onPrimary: "#0f172a", + primaryContainer: "#1e293b", + onPrimaryContainer: "#f1f5f9", + secondary: "#c2d6f5", // Tonal lighter Soft Blue + onSecondary: "#2e405a", + secondaryContainer: "#4a5d77", + onSecondaryContainer: "#d3e3ff", + tertiary: "#e5d8f0", // Tonal lighter Lavender + onTertiary: "#4a4154", + background: "#000000", // Pure black + onBackground: "#f1f4f3", + surface: "#000000", + onSurface: "#f1f4f3", + surfaceVariant: "#1a1a1a", + onSurfaceVariant: "#acb3b2", + outline: "#acb3b2", + outlineVariant: "#596060", + error: "#fa746f", + onError: "#6e0a12", + }, +}; + +export type ThemeColors = typeof COLORS.light; + +export const SPACING = { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + xxl: 48, + giant: 64, +}; + +export const ROUNDNESS = { + none: 0, + sm: 4, + md: 8, + lg: 12, + xl: 24, + full: 9999, +}; + +export const FONTS = { + headline: "Manrope_700Bold", + body: "Manrope_400Regular", + label: "PlusJakartaSans_500Medium", + labelSm: "PlusJakartaSans_700Bold", +}; + +export const GRID_STYLE = { + ghostBorder: (scheme: "light" | "dark") => + scheme === "light" ? "rgba(51, 65, 85, 0.1)" : "rgba(148, 163, 184, 0.1)", +}; diff --git a/src/db/database.ts b/src/db/database.ts new file mode 100644 index 0000000..816f93c --- /dev/null +++ b/src/db/database.ts @@ -0,0 +1,195 @@ +import * as SQLite from 'expo-sqlite'; + +export const DATABASE_NAME = 'batsir.db'; + +let dbInstance: SQLite.SQLiteDatabase | null = null; +let initPromise: Promise | null = null; + +export const initDatabase = async () => { + if (initPromise) return initPromise; + + initPromise = (async () => { + if (!dbInstance) { + dbInstance = await SQLite.openDatabaseAsync(DATABASE_NAME); + } + const db = dbInstance; + + await db.execAsync('PRAGMA journal_mode = WAL;'); + + const tables = [ + `CREATE TABLE IF NOT EXISTS habits ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + title TEXT NOT NULL, + frequency TEXT NOT NULL DEFAULT 'daily', + preferred_time TEXT, + weekend_flexibility INTEGER DEFAULT 0, + is_active INTEGER DEFAULT 1, + current_streak INTEGER DEFAULT 0, + max_streak INTEGER DEFAULT 0, + two_minute_version TEXT, + location TEXT, + anchor_habit_id TEXT, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS schedules ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + date TEXT NOT NULL, + time_blocks TEXT NOT NULL DEFAULT '[]', + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS logs ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + habit_id TEXT NOT NULL, + status TEXT NOT NULL, + logged_at TEXT DEFAULT (datetime('now')), + created_at TEXT DEFAULT (datetime('now')), + FOREIGN KEY (habit_id) REFERENCES habits (id) ON DELETE CASCADE + );`, + `CREATE TABLE IF NOT EXISTS sync_queue ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + table_name TEXT NOT NULL, + operation TEXT NOT NULL, + payload TEXT NOT NULL, + created_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS shortcuts ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + title TEXT NOT NULL, + url TEXT NOT NULL, + icon TEXT, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY NOT NULL, + value TEXT NOT NULL + );`, + `CREATE TABLE IF NOT EXISTS tasks ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + title TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'todo', + estimated_sessions INTEGER DEFAULT 1, + completed_sessions INTEGER DEFAULT 0, + tag TEXT, + todos TEXT NOT NULL DEFAULT '[]', + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS sync_history ( + old_id TEXT PRIMARY KEY NOT NULL, + new_id TEXT NOT NULL, + table_name TEXT NOT NULL, + synced_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS books ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + title TEXT NOT NULL, + author TEXT, + total_pages INTEGER DEFAULT 0, + current_page INTEGER DEFAULT 0, + last_page_read INTEGER DEFAULT 0, + file_uri TEXT, + cover_uri TEXT, + status TEXT DEFAULT 'want_to_read', + synthesis TEXT, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')) + );`, + `CREATE TABLE IF NOT EXISTS reading_logs ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + book_id TEXT NOT NULL, + start_page INTEGER DEFAULT 0, + end_page INTEGER DEFAULT 0, + pages_read INTEGER DEFAULT 0, + duration_minutes REAL DEFAULT 0, + duration_seconds REAL DEFAULT 0, + logged_at TEXT DEFAULT (datetime('now')), + FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE + );`, + `CREATE TABLE IF NOT EXISTS reading_sessions ( + book_id TEXT PRIMARY KEY NOT NULL, + start_time INTEGER NOT NULL, + start_page INTEGER NOT NULL, + last_update_time INTEGER NOT NULL, + accumulated_time INTEGER NOT NULL, + notes TEXT NOT NULL, + FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE + );`, + `CREATE TABLE IF NOT EXISTS bookmarks ( + id TEXT PRIMARY KEY NOT NULL, + user_id TEXT, + book_id TEXT NOT NULL, + page_number INTEGER NOT NULL, + note TEXT, + created_at TEXT DEFAULT (datetime('now')), + FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE + );` + ]; + + for (const tableSql of tables) { + try { + await db.execAsync(tableSql); + } catch (err) { + console.error('Error creating table:', err, tableSql); + throw err; + } + } + + try { + const habitsInfo = await db.getAllAsync(`PRAGMA table_info(habits)`); + const hCols = (habitsInfo as any[]).map(c => c.name); + if (!hCols.includes('preferred_time')) await db.execAsync(`ALTER TABLE habits ADD COLUMN preferred_time TEXT;`); + if (!hCols.includes('weekend_flexibility')) await db.execAsync(`ALTER TABLE habits ADD COLUMN weekend_flexibility INTEGER DEFAULT 0;`); + if (!hCols.includes('current_streak')) await db.execAsync(`ALTER TABLE habits ADD COLUMN current_streak INTEGER DEFAULT 0;`); + if (!hCols.includes('max_streak')) await db.execAsync(`ALTER TABLE habits ADD COLUMN max_streak INTEGER DEFAULT 0;`); + if (!hCols.includes('two_minute_version')) await db.execAsync(`ALTER TABLE habits ADD COLUMN two_minute_version TEXT;`); + if (!hCols.includes('location')) await db.execAsync(`ALTER TABLE habits ADD COLUMN location TEXT;`); + if (!hCols.includes('anchor_habit_id')) await db.execAsync(`ALTER TABLE habits ADD COLUMN anchor_habit_id TEXT;`); + + const taskInfo = await db.getAllAsync(`PRAGMA table_info(tasks)`); + const tCols = (taskInfo as any[]).map(c => c.name); + if (!tCols.includes('todos')) await db.execAsync(`ALTER TABLE tasks ADD COLUMN todos TEXT NOT NULL DEFAULT '[]';`); + + const readingLogInfo = await db.getAllAsync(`PRAGMA table_info(reading_logs)`); + const rlCols = (readingLogInfo as any[]).map(c => c.name); + if (!rlCols.includes('user_id')) await db.execAsync(`ALTER TABLE reading_logs ADD COLUMN user_id TEXT;`); + if (!rlCols.includes('duration_seconds')) await db.execAsync(`ALTER TABLE reading_logs ADD COLUMN duration_seconds REAL DEFAULT 0;`); + if (!rlCols.includes('start_page')) await db.execAsync(`ALTER TABLE reading_logs ADD COLUMN start_page INTEGER DEFAULT 0;`); + if (!rlCols.includes('end_page')) await db.execAsync(`ALTER TABLE reading_logs ADD COLUMN end_page INTEGER DEFAULT 0;`); + + const bookmarksInfo = await db.getAllAsync(`PRAGMA table_info(bookmarks)`); + const bCols = (bookmarksInfo as any[]).map(c => c.name); + if (bCols.length > 0 && !bCols.includes('user_id')) { + await db.execAsync(`ALTER TABLE bookmarks ADD COLUMN user_id TEXT;`); + } + + const logsInfo = await db.getAllAsync(`PRAGMA table_info(logs)`); + const lCols = (logsInfo as any[]).map(c => c.name); + if (!lCols.includes('user_id')) await db.execAsync(`ALTER TABLE logs ADD COLUMN user_id TEXT;`); + + const booksInfo = await db.getAllAsync(`PRAGMA table_info(books)`); + const bookCols = (booksInfo as any[]).map(c => c.name); + if (!bookCols.includes('last_page_read')) await db.execAsync(`ALTER TABLE books ADD COLUMN last_page_read INTEGER DEFAULT 0;`); + if (!bookCols.includes('synthesis')) await db.execAsync(`ALTER TABLE books ADD COLUMN synthesis TEXT;`); + } catch (error) { + console.error('Migration error:', error); + } + + return db; + })(); + + return initPromise; +}; + +export const getDb = async () => { + return initDatabase(); +}; diff --git a/src/db/database.web.ts b/src/db/database.web.ts new file mode 100644 index 0000000..0c1458a --- /dev/null +++ b/src/db/database.web.ts @@ -0,0 +1,12 @@ +export const DATABASE_NAME = "batsir.db"; + +export const initDatabase = async () => { + // SQLite is not directly available in standard web without extra configuration + // For web, we usually mock or use a different adapter. + // In Expo 51+, expo-sqlite handles web via an Op-SQLite or similar wrapper if configured. + return null; +}; + +export const getDb = async () => { + return null; +}; diff --git a/src/hooks/useAIChat.ts b/src/hooks/useAIChat.ts new file mode 100644 index 0000000..aa35a3c --- /dev/null +++ b/src/hooks/useAIChat.ts @@ -0,0 +1,118 @@ +import { buildSystemPrompt, callAiAssistant } from "@/src/services/aiService"; +import { + createAssistantMessage, + createUserMessage, + saveChatMessage, + serializeHistoryMessage, +} from "@/src/services/chatService"; +import { ChatMessage } from "@/src/types/chat"; +import type { MutableRefObject } from "react"; +import { useCallback, useState } from "react"; + +interface UseAIChatProps { + userId: string | null; + identityAnchor: string; + habitContext: string; + messagesRef: MutableRefObject; + appendMessage: (message: ChatMessage | ChatMessage[]) => void; + updateMessage: (id: string, patch: Partial) => void; +} + +export function useAIChat({ + userId, + identityAnchor, + habitContext, + messagesRef, + appendMessage, + updateMessage, +}: UseAIChatProps) { + const [isSending, setIsSending] = useState(false); + const [error, setError] = useState(null); + + const sendMessage = useCallback( + async (rawText: string) => { + console.log("[useAIChat] sendMessage started", { rawText }); + const content = rawText.trim(); + if (!content) return; + if (!userId) { + console.warn("[useAIChat] No userId found"); + setError(new Error("Authentication required to chat.")); + return; + } + + const userMessage = createUserMessage(content); + const pendingAssistantMessage = createAssistantMessage("", "sending"); + + console.log("[useAIChat] Appending user and pending messages"); + appendMessage([userMessage, pendingAssistantMessage]); + + const systemPrompt = buildSystemPrompt(identityAnchor, habitContext); + setIsSending(true); + setError(null); + + const conversation = [...[...messagesRef.current].reverse(), userMessage].map( + serializeHistoryMessage, + ); + console.log("[useAIChat] Prepared conversation length:", conversation.length); + + try { + await saveChatMessage(userId, userMessage); + console.log("[useAIChat] User message saved to DB"); + } catch (saveError) { + console.warn("[useAIChat] Unable to persist user message:", saveError); + } + + try { + console.log("[useAIChat] Calling AI Assistant..."); + const response = await callAiAssistant(conversation, systemPrompt); + console.log("[useAIChat] AI Response received", { hasReply: !!response.reply }); + + const reply = + response.reply ?? + "I'm here to help — let's try again with a clearer request."; + + updateMessage(pendingAssistantMessage.id, { + content: reply, + status: "sent", + createdAt: Date.now(), + }); + console.log("[useAIChat] Updated UI with AI reply"); + + try { + await saveChatMessage(userId, { + ...pendingAssistantMessage, + content: reply, + status: "sent", + createdAt: Date.now(), + }); + console.log("[useAIChat] AI response saved to DB"); + } catch (saveError) { + console.warn("[useAIChat] Unable to persist assistant response:", saveError); + } + } catch (aiError) { + console.error("[useAIChat] AI Error:", aiError); + const fallback = + "I'm sorry, I'm having trouble connecting to the system. Please try again in a moment."; + updateMessage(pendingAssistantMessage.id, { + content: fallback, + status: "error", + createdAt: Date.now(), + }); + setError(aiError as Error); + } finally { + setIsSending(false); + console.log("[useAIChat] sendMessage finished"); + } + }, + [ + appendMessage, + habitContext, + identityAnchor, + messagesRef, + updateMessage, + userId, + ], + ); + + return { isSending, sendMessage, error }; +} diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts new file mode 100644 index 0000000..814b63f --- /dev/null +++ b/src/hooks/useAuth.ts @@ -0,0 +1,41 @@ +import { useState, useEffect } from 'react'; +import { supabase } from '../lib/supabase'; +import { Session, User } from '@supabase/supabase-js'; + +export function useAuth() { + const [session, setSession] = useState(null); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Check active sessions and sets the user + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session); + setUser(session?.user ?? null); + setLoading(false); + }); + + // Listen for changes on auth state + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setSession(session); + setUser(session?.user ?? null); + setLoading(false); + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); + + const signOut = async () => { + await supabase.auth.signOut(); + }; + + return { + session, + user, + loading, + signOut, + isAuthenticated: !!user, + }; +} diff --git a/src/hooks/useAutoHideTabBar.tsx b/src/hooks/useAutoHideTabBar.tsx new file mode 100644 index 0000000..af7c75a --- /dev/null +++ b/src/hooks/useAutoHideTabBar.tsx @@ -0,0 +1,120 @@ +import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { NativeScrollEvent, NativeSyntheticEvent, Platform, Keyboard } from 'react-native'; +import { usePathname } from 'expo-router'; + +type AutoHideScrollContextType = { + tabBarVisible: boolean; + showTabBar: () => void; + hideTabBar: () => void; + onScroll: (event: NativeSyntheticEvent) => void; +}; + +const AutoHideScrollContext = createContext(undefined); + +const HIDE_THRESHOLD = 20; // Requirement: 15-20px +const SHOW_THRESHOLD = 15; + +export function TabBarVisibilityProvider({ children }: { children: React.ReactNode }) { + const [tabBarVisible, setTabBarVisible] = useState(true); + const [isKeyboardVisible, setIsKeyboardVisible] = useState(false); + const lastScrollYRef = useRef(0); + const pathname = usePathname(); + + // Keyboard awareness - Requirement: Don't hide if keyboard is open + useEffect(() => { + const showSubscription = Keyboard.addListener( + Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', + () => { + setIsKeyboardVisible(true); + setTabBarVisible(true); // Always show when keyboard comes up + } + ); + const hideSubscription = Keyboard.addListener( + Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', + () => { + setIsKeyboardVisible(false); + } + ); + + return () => { + showSubscription.remove(); + hideSubscription.remove(); + }; + }, []); + + // Reset visibility on navigation + useEffect(() => { + setTabBarVisible(true); + lastScrollYRef.current = 0; + }, [pathname]); + + const showTabBar = useCallback(() => { + setTabBarVisible(true); + }, []); + + const hideTabBar = useCallback(() => { + setTabBarVisible(false); + }, []); + + const onScroll = useCallback((event: NativeSyntheticEvent) => { + // Requirement: Don't hide if keyboard is open + if (isKeyboardVisible) return; + + const offsetY = event.nativeEvent.contentOffset.y; + const contentHeight = event.nativeEvent.contentSize.height; + const layoutHeight = event.nativeEvent.layoutMeasurement.height; + + // Requirement: Reset when scroll stops at top (offset = 0) + if (offsetY <= 0) { + setTabBarVisible(true); + lastScrollYRef.current = 0; + return; + } + + // Don't hide if content is smaller than screen + if (contentHeight <= layoutHeight) { + setTabBarVisible(true); + return; + } + + // Ignore bounce effects at bottom + if (offsetY > contentHeight - layoutHeight) { + return; + } + + const delta = offsetY - lastScrollYRef.current; + + if (delta > HIDE_THRESHOLD) { + // Scrolling down (content goes up) -> hide full bar + setTabBarVisible(false); + lastScrollYRef.current = offsetY; + } else if (delta < -SHOW_THRESHOLD) { + // Scrolling up (content comes down) -> show full bar + setTabBarVisible(true); + lastScrollYRef.current = offsetY; + } + }, [isKeyboardVisible]); + + const value = useMemo( + () => ({ tabBarVisible, showTabBar, hideTabBar, onScroll }), + [tabBarVisible, showTabBar, hideTabBar, onScroll] + ); + + return {children}; +} + +export function useTabBarVisibility() { + const context = useContext(AutoHideScrollContext); + if (!context) { + throw new Error('useTabBarVisibility must be used within TabBarVisibilityProvider'); + } + return context; +} + +export function useAutoHideOnScroll() { + const context = useTabBarVisibility(); + return { + onScroll: context.onScroll, + scrollEventThrottle: 16, + }; +} diff --git a/src/hooks/useChatHistory.ts b/src/hooks/useChatHistory.ts new file mode 100644 index 0000000..11de5a5 --- /dev/null +++ b/src/hooks/useChatHistory.ts @@ -0,0 +1,87 @@ +import { + createGreetingMessage, + fetchChatHistory, +} from "@/src/services/chatService"; +import { ChatMessage } from "@/src/types/chat"; +import { useCallback, useEffect, useRef, useState } from "react"; + +export function useChatHistory(userId: string | null) { + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const messagesRef = useRef(messages); + + useEffect(() => { + messagesRef.current = messages; + }, [messages]); + + const setStableMessages = useCallback( + (action: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[])) => { + setMessages((prev) => { + const next = typeof action === "function" ? action(prev) : action; + messagesRef.current = next; + return next; + }); + }, + [], + ); + + const appendMessage = useCallback( + (next: ChatMessage | ChatMessage[]) => { + setStableMessages((prev) => { + const nextMessages = Array.isArray(next) ? next : [next]; + // Prepend so the newest messages are at index 0 + return [...nextMessages.reverse(), ...prev]; + }); + }, + [setStableMessages], + ); + + const updateMessage = useCallback( + (id: string, patch: Partial) => { + setStableMessages((prev) => + prev.map((item) => (item.id === id ? { ...item, ...patch } : item)), + ); + }, + [setStableMessages], + ); + + const refresh = useCallback(async () => { + if (!userId) { + setStableMessages([createGreetingMessage()]); + setIsLoading(false); + return; + } + + setIsLoading(true); + try { + const history = await fetchChatHistory(userId); + if (history.length > 0) { + setStableMessages(history); + } else { + setStableMessages([createGreetingMessage()]); + } + setError(null); + } catch (err) { + console.error("Chat history fetch failed:", err); + setError(err as Error); + setStableMessages([createGreetingMessage()]); + } finally { + setIsLoading(false); + } + }, [userId, setStableMessages]); + + useEffect(() => { + refresh(); + }, [refresh]); + + return { + messages, + messagesRef, + isLoading, + error, + appendMessage, + updateMessage, + refresh, + }; +} diff --git a/src/hooks/useData.ts b/src/hooks/useData.ts new file mode 100644 index 0000000..2b2c336 --- /dev/null +++ b/src/hooks/useData.ts @@ -0,0 +1,108 @@ +import { useCallback, useEffect, useState, useRef } from "react"; +import { getDb } from "../db/database"; +import { subscribeToDatabaseChanges } from "../lib/sync"; + +export function useData(query: string, params: any[] = []) { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const paramsRef = useRef(JSON.stringify(params)); + const queryRef = useRef(query); + const dataRef = useRef(JSON.stringify([])); + + const fetchData = useCallback( + async (isCancelled: () => boolean) => { + // Don't set loading true if we already have data to avoid jitter + // unless it's the first load + try { + const db = await getDb(); + if (!db) { + if (!isCancelled()) setData([]); + return; + } + + const currentParams = JSON.parse(paramsRef.current); + const sanitizedParams = currentParams.map((p: any) => + p === null || p === undefined ? null : String(p), + ); + + if (!queryRef.current) { + console.warn("useData: query is empty, skipping execution."); + if (!isCancelled()) { + setData([]); + setLoading(false); + } + return; + } + + const result = await db.getAllAsync(queryRef.current, sanitizedParams); + + if (!isCancelled()) { + const stringifiedResult = JSON.stringify(result); + // ONLY update state if the data actually changed + if (stringifiedResult !== dataRef.current) { + dataRef.current = stringifiedResult; + setData(result as T[]); + // Log a bit more context to help debugging + const shortQuery = queryRef.current.replace(/\s+/g, ' ').substring(0, 80); + console.log("DATABASE DATA UPDATED", { query: shortQuery, count: result.length }); + } + setError(null); + } + } catch (err) { + if (!isCancelled()) { + console.error("Database query error:", err, queryRef.current, paramsRef.current); + setError(err as Error); + } + } finally { + if (!isCancelled()) { + setLoading(false); + } + } + }, + [], + ); + + // Handle parameter changes + useEffect(() => { + const stringifiedParams = JSON.stringify(params); + if (stringifiedParams !== paramsRef.current || query !== queryRef.current) { + paramsRef.current = stringifiedParams; + queryRef.current = query; + setLoading(true); + fetchData(() => false); + } + }, [query, JSON.stringify(params), fetchData]); + + // Handle external database changes with a small debounce + useEffect(() => { + let cancelled = false; + const isCancelled = () => cancelled; + let timeout: any = null; + + fetchData(isCancelled); + + const unsubscribe = subscribeToDatabaseChanges(() => { + if (cancelled) return; + + // Debounce the re-fetch to handle batch mutations + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + if (!cancelled) { + fetchData(isCancelled); + } + }, 50); + }); + + return () => { + cancelled = true; + if (timeout) clearTimeout(timeout); + unsubscribe(); + }; + }, [fetchData]); + + const refresh = useCallback(() => fetchData(() => false), [fetchData]); + + return { data, loading, error, refresh }; +} diff --git a/src/hooks/useHabitSummary.ts b/src/hooks/useHabitSummary.ts new file mode 100644 index 0000000..f14f291 --- /dev/null +++ b/src/hooks/useHabitSummary.ts @@ -0,0 +1,33 @@ +import { useData } from "@/src/hooks/useData"; +import { useMemo } from "react"; + +interface HabitRecord { + title: string; + current_streak: number; + two_minute_version: string; +} + +export function useHabitSummary(userId: string) { + const { data: habits = [] } = useData( + "SELECT title, current_streak, two_minute_version FROM habits WHERE is_active = 1 AND (user_id = ? OR user_id IS NULL)", + [userId], + ); + + const habitContext = useMemo(() => { + if (habits.length === 0) { + return "User has no active habits yet."; + } + + return ( + "Current Habits:\n" + + habits + .map( + (habit) => + `- ${habit.title} (Streak: ${habit.current_streak}, 2-min: ${habit.two_minute_version})`, + ) + .join("\n") + ); + }, [habits]); + + return { habitContext }; +} diff --git a/src/hooks/useRequireAuth.ts b/src/hooks/useRequireAuth.ts new file mode 100644 index 0000000..64b4e07 --- /dev/null +++ b/src/hooks/useRequireAuth.ts @@ -0,0 +1,13 @@ +import type { User } from "@supabase/supabase-js"; +import { useRouter } from "expo-router"; +import { useEffect } from "react"; + +export function useRequireAuth(user: User | null, loading: boolean) { + const router = useRouter(); + + useEffect(() => { + if (!loading && !user) { + router.replace("/login"); + } + }, [loading, router, user]); +} diff --git a/src/hooks/useSync.ts b/src/hooks/useSync.ts new file mode 100644 index 0000000..313aba5 --- /dev/null +++ b/src/hooks/useSync.ts @@ -0,0 +1,34 @@ +import NetInfo from "@react-native-community/netinfo"; +import { useEffect } from "react"; +import { supabase } from "../lib/supabase"; +import { syncWithSupabase } from "../lib/sync"; + +export function useSync() { + useEffect(() => { + // Initial sync + syncWithSupabase(); + + // Sync on network status change + const unsubscribeNetInfo = NetInfo.addEventListener((state) => { + if (state.isConnected) { + syncWithSupabase(); + } + }); + + // Sync on auth state changes so guest data is migrated when signing in + const { + data: { subscription: authSubscription }, + } = supabase.auth.onAuthStateChange(() => { + syncWithSupabase(); + }); + + // Periodic sync every 5 minutes + const interval = setInterval(syncWithSupabase, 5 * 60 * 1000); + + return () => { + unsubscribeNetInfo(); + authSubscription.unsubscribe(); + clearInterval(interval); + }; + }, []); +} diff --git a/src/hooks/useSync.web.ts b/src/hooks/useSync.web.ts new file mode 100644 index 0000000..8b6313d --- /dev/null +++ b/src/hooks/useSync.web.ts @@ -0,0 +1,7 @@ +import { useEffect } from "react"; + +export function useSync() { + useEffect(() => { + // Sync is skipped on web because local SQLite and offline queueing are not supported. + }, []); +} diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx new file mode 100644 index 0000000..71d1865 --- /dev/null +++ b/src/hooks/useTheme.tsx @@ -0,0 +1,245 @@ +import React, { + ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { useColorScheme } from "react-native"; +import { ACCENTS, AccentKey, COLORS, ThemeColors } from "../constants/Theme"; +import { getDb } from "../db/database"; +import { supabase } from "../lib/supabase"; + +const ACCENT_STORAGE_KEY = "accent_key"; +const THEME_MODE_STORAGE_KEY = "theme_mode"; +const FOCUS_GOAL_KEY = "daily_focus_goal"; +const SPRINT_DURATION_KEY = "sprint_duration"; +const IDENTITY_ANCHOR_KEY = "identity_anchor"; +const DISPLAY_NAME_KEY = "display_name"; + +export type ThemeMode = "light" | "dark" | "system"; + +interface ThemeContextType { + colors: ThemeColors; + colorScheme: "light" | "dark"; + isDark: boolean; + accentKey: AccentKey; + updateAccent: (key: AccentKey) => Promise; + availableAccents: typeof ACCENTS; + themeMode: ThemeMode; + updateThemeMode: (mode: ThemeMode) => Promise; + focusGoal: number; + updateFocusGoal: (goal: number) => Promise; + sprintDuration: number; + updateSprintDuration: (mins: number) => Promise; + identityAnchor: string; + updateIdentityAnchor: (anchor: string) => Promise; + displayName: string; + updateDisplayName: (name: string) => Promise; + isLoaded: boolean; +} + +const ThemeContext = createContext(undefined); + +async function getSetting(key: string, defaultValue: string): Promise { + try { + const db = await getDb(); + const result = await db.getFirstAsync<{ value: string }>( + "SELECT value FROM settings WHERE key = ?", + [key], + ); + return result ? result.value : defaultValue; + } catch (e) { + return defaultValue; + } +} + +async function saveSetting(key: string, value: string): Promise { + try { + const db = await getDb(); + await db.runAsync( + "INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", + [key, value], + ); + + // Attempt to sync to Supabase profile preferences + const { + data: { user }, + } = await supabase.auth.getUser(); + if (user) { + const { data: profile } = await supabase + .from("profiles") + .select("preferences") + .eq("id", user.id) + .single(); + const prefs = profile?.preferences || {}; + await supabase + .from("profiles") + .update({ + preferences: { ...prefs, [key]: value }, + }) + .eq("id", user.id); + } + } catch (e) { + console.error(`Failed to save setting ${key}`, e); + } +} + +export const BatsirThemeProvider = ({ children }: { children: ReactNode }) => { + const systemColorScheme = useColorScheme() ?? "light"; + const [accentKey, setAccentKey] = useState("slate"); + const [themeMode, setThemeMode] = useState("system"); + const [focusGoal, setFocusGoal] = useState(8); + const [sprintDuration, setSprintDuration] = useState(25); + const [identityAnchor, setIdentityAnchor] = useState( + "The Disciplined Creator", + ); + const [displayName, setDisplayName] = useState(""); + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + const loadSettings = async () => { + try { + const storedAccent = await getSetting(ACCENT_STORAGE_KEY, "slate"); + const storedMode = await getSetting(THEME_MODE_STORAGE_KEY, "system"); + const storedGoal = await getSetting(FOCUS_GOAL_KEY, "8"); + const storedDuration = await getSetting(SPRINT_DURATION_KEY, "25"); + const storedAnchor = await getSetting( + IDENTITY_ANCHOR_KEY, + "The Disciplined Creator", + ); + const storedName = await getSetting(DISPLAY_NAME_KEY, ""); + + setAccentKey(storedAccent as AccentKey); + setThemeMode(storedMode as ThemeMode); + setFocusGoal(parseInt(storedGoal)); + setSprintDuration(parseInt(storedDuration)); + setIdentityAnchor(storedAnchor); + setDisplayName(storedName); + } catch (e) { + console.error("Failed to load theme settings", e); + } finally { + setIsLoaded(true); + } + }; + loadSettings(); + }, []); + + const updateAccent = useCallback(async (key: AccentKey) => { + setAccentKey(key); + await saveSetting(ACCENT_STORAGE_KEY, key); + }, []); + + const updateThemeMode = useCallback(async (mode: ThemeMode) => { + setThemeMode(mode); + await saveSetting(THEME_MODE_STORAGE_KEY, mode); + }, []); + + const updateFocusGoal = useCallback(async (goal: number) => { + setFocusGoal(goal); + await saveSetting(FOCUS_GOAL_KEY, goal.toString()); + }, []); + + const updateSprintDuration = useCallback(async (mins: number) => { + setSprintDuration(mins); + await saveSetting(SPRINT_DURATION_KEY, mins.toString()); + }, []); + + const updateIdentityAnchor = useCallback(async (anchor: string) => { + setIdentityAnchor(anchor); + await saveSetting(IDENTITY_ANCHOR_KEY, anchor); + }, []); + + const updateDisplayName = useCallback(async (name: string) => { + setDisplayName(name); + await saveSetting(DISPLAY_NAME_KEY, name); + + // Also update public.profiles table directly for display_name column + try { + const { + data: { user }, + } = await supabase.auth.getUser(); + if (user) { + await supabase + .from("profiles") + .update({ display_name: name }) + .eq("id", user.id); + } + } catch (e) { + console.error("Failed to update display_name in profiles", e); + } + }, []); + + const colorScheme = themeMode === "system" ? systemColorScheme : themeMode; + const isDark = colorScheme === "dark"; + + const colors = useMemo(() => { + const baseColors = COLORS[colorScheme as keyof typeof COLORS]; + const accent = ACCENTS[accentKey]; + return { + ...baseColors, + primary: accent.primary, + onPrimary: "#FFFFFF", + primaryContainer: isDark + ? baseColors.primaryContainer + : accent.primaryContainer, + onPrimaryContainer: isDark + ? baseColors.onPrimaryContainer + : accent.onPrimaryContainer, + }; + }, [colorScheme, accentKey, isDark]); + + const value = useMemo( + () => ({ + colors, + colorScheme: colorScheme as "light" | "dark", + isDark, + accentKey, + updateAccent, + availableAccents: ACCENTS, + themeMode, + updateThemeMode, + focusGoal, + updateFocusGoal, + sprintDuration, + updateSprintDuration, + identityAnchor, + updateIdentityAnchor, + displayName, + updateDisplayName, + isLoaded, + }), + [ + colors, + colorScheme, + isDark, + accentKey, + updateAccent, + themeMode, + updateThemeMode, + focusGoal, + updateFocusGoal, + sprintDuration, + updateSprintDuration, + identityAnchor, + updateIdentityAnchor, + displayName, + updateDisplayName, + isLoaded, + ], + ); + + return ( + {children} + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + return context; +}; diff --git a/src/lib/ai.ts b/src/lib/ai.ts new file mode 100644 index 0000000..5c1d081 --- /dev/null +++ b/src/lib/ai.ts @@ -0,0 +1,26 @@ +import { supabase } from './supabase'; + +export interface AIMessage { + text: string; + sender: 'user' | 'ai'; +} + +export const callAiAssistant = async (messages: AIMessage[], systemPrompt?: string) => { + try { + // Supabase client automatically includes the session JWT in the Authorization header + // if a session exists and AsyncStorage is configured. + const { data, error } = await supabase.functions.invoke('chat-ai', { + body: { messages, systemPrompt }, + }); + + if (error) { + console.error('Supabase Function Error:', error); + throw error; + } + + return data; + } catch (error) { + console.error('AI Assistant Error:', error); + throw error; + } +}; diff --git a/src/lib/date-utils.ts b/src/lib/date-utils.ts new file mode 100644 index 0000000..23dc71d --- /dev/null +++ b/src/lib/date-utils.ts @@ -0,0 +1,18 @@ +/** + * Returns the current date in YYYY-MM-DD format based on local time. + */ +export const getLocalDateString = (date: Date = new Date()) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +}; + +/** + * Returns yesterday's date in YYYY-MM-DD format based on local time. + */ +export const getYesterdayDateString = () => { + const d = new Date(); + d.setDate(d.getDate() - 1); + return getLocalDateString(d); +}; diff --git a/src/lib/file-utils.ts b/src/lib/file-utils.ts new file mode 100644 index 0000000..f7b5462 --- /dev/null +++ b/src/lib/file-utils.ts @@ -0,0 +1,135 @@ +import * as FileSystem from 'expo-file-system/legacy'; +import { supabase } from './supabase'; + +const SUPABASE_STORAGE_URL = 'https://xhkkjelhmvepsouuibvs.supabase.co/storage/v1/object/public/books'; + +export const getDocumentDirectory = () => { + return FileSystem.documentDirectory || ''; +}; + +/** + * Resolves a file URI that might be stored as an absolute path or a relative path. + * In iOS, absolute paths change between app launches due to the container UUID changing. + * This utility ensures we always use the current document directory. + */ +export const resolveFileUri = (uri: string | null | undefined): string => { + if (!uri) return ''; + + // If it's already a full URL or a resolved file path + if (uri.startsWith('http') || uri.startsWith('file://') || uri.startsWith('content://')) { + // If it's an absolute path but might be stale (iOS container ID change) + if (uri.startsWith('file:///')) { + const docDir = getDocumentDirectory(); + const booksMatch = uri.match(/\/books\/.+$/); + if (booksMatch && docDir) { + const relativePath = booksMatch[0].startsWith('/') ? booksMatch[0].substring(1) : booksMatch[0]; + return `${docDir}${relativePath}`; + } + } + return uri; + } + + const docDir = getDocumentDirectory(); + + if (docDir) { + // If it's already a relative path (doesn't start with file:// or /) + if (!uri.startsWith('file://') && !uri.startsWith('/')) { + return `${docDir}${uri}`; + } + + // If it's an absolute path, try to extract the relative part (books/...) + // This handles stale paths from previous app containers on iOS + const booksMatch = uri.match(/\/books\/.+$/); + if (booksMatch) { + const relativePath = booksMatch[0].startsWith('/') ? booksMatch[0].substring(1) : booksMatch[0]; + return `${docDir}${relativePath}`; + } + + return uri.startsWith('/') ? `file://${uri}` : uri; + } + + // Fallback to Supabase Storage if local directory is not available (e.g. web or init issue) + let remotePath = uri; + if (uri.startsWith('books/')) { + remotePath = uri.replace('books/', ''); + } else if (uri.startsWith('/books/')) { + remotePath = uri.replace('/books/', ''); + } + + return `${SUPABASE_STORAGE_URL}/${remotePath}`; +}; + +/** + * Ensures a book is available locally by downloading it from Supabase if missing. + */ +export const downloadBook = async (relativeUri: string): Promise => { + const docDir = getDocumentDirectory(); + if (!docDir) throw new Error('Document directory not available'); + + const localUri = resolveFileUri(relativeUri); + const fileInfo = await FileSystem.getInfoAsync(localUri); + + if (fileInfo.exists) { + return localUri; + } + + // Create directory if it doesn't exist + const dirPath = localUri.substring(0, localUri.lastIndexOf('/')); + await FileSystem.makeDirectoryAsync(dirPath, { intermediates: true }); + + // Construct download URL + let remotePath = relativeUri; + if (relativeUri.startsWith('books/')) { + remotePath = relativeUri.replace('books/', ''); + } else if (relativeUri.startsWith('/books/')) { + remotePath = relativeUri.replace('/books/', ''); + } + + // Try to get a signed URL first (in case the bucket is private) + let downloadUrl = ''; + const { data: signedData, error: signedError } = await supabase.storage.from('books').createSignedUrl(remotePath, 3600); + + if (signedError) { + console.error('[downloadBook] Signed URL error:', signedError); + } + + if (signedData?.signedUrl) { + downloadUrl = signedData.signedUrl; + } else { + // Fallback to public URL + const { data: publicData } = supabase.storage.from('books').getPublicUrl(remotePath); + downloadUrl = publicData.publicUrl; + } + + console.log('[downloadBook] Downloading from:', downloadUrl); + console.log('[downloadBook] To:', localUri); + + try { + const downloadRes = await FileSystem.downloadAsync(downloadUrl, localUri); + + if (downloadRes.status !== 200) { + // Clean up the potentially empty/corrupted file + await FileSystem.deleteAsync(localUri, { idempotent: true }).catch(() => {}); + throw new Error(`Download failed with status ${downloadRes.status}`); + } + } catch (e: any) { + console.error('[downloadBook] Download error:', e); + throw e; + } + + return localUri; +}; + +/** + * Gets the relative path for a file URI to be stored in the database. + */ +export const getRelativePath = (uri: string): string => { + if (!uri) return ''; + + const booksMatch = uri.match(/\/books\/.+$/); + if (booksMatch) { + return booksMatch[0].startsWith('/') ? booksMatch[0].substring(1) : booksMatch[0]; + } + + return uri; +}; diff --git a/src/lib/habit-logic.ts b/src/lib/habit-logic.ts new file mode 100644 index 0000000..fc9490a --- /dev/null +++ b/src/lib/habit-logic.ts @@ -0,0 +1,195 @@ +import { getDb } from '../db/database'; + +export interface HabitStreakInfo { + currentStreak: number; + maxStreak: number; + isDoneToday: boolean; + lastLoggedDate: string | null; +} + +/** + * Helper to get local YYYY-MM-DD string + */ +function getLocalDayString(date: Date = new Date()): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +/** + * Helper to check if a local date string is a weekend + */ +function isWeekend(dateStr: string): boolean { + const [year, month, day] = dateStr.split('-').map(Number); + const d = new Date(year, month - 1, day); + const dayOfWeek = d.getDay(); + return dayOfWeek === 0 || dayOfWeek === 6; // Sunday = 0, Saturday = 6 +} + +/** + * Calculates streak info for a habit based on its logs + */ +export function calculateStreak( + logDates: string[], + weekendFlexibility: boolean = false +): HabitStreakInfo { + if (logDates.length === 0) { + return { currentStreak: 0, maxStreak: 0, isDoneToday: false, lastLoggedDate: null }; + } + + // Normalize dates to local YYYY-MM-DD and sort descending + const uniqueDates = [...new Set(logDates.map(d => { + // Handle ISO (T) or SQLite (space) format + const datePart = d.split(/[T ]/)[0]; + if (d.includes('T') || d.includes(':')) { + // If it looks like a full timestamp, we treat it as UTC and convert to LOCAL YYYY-MM-DD + const dateObj = new Date(d.replace(' ', 'T')); + if (!isNaN(dateObj.getTime())) { + return getLocalDayString(dateObj); + } + } + return datePart; // Fallback to the date part + }))].sort((a, b) => b.localeCompare(a)); + + const todayStr = getLocalDayString(); + const yesterdayDate = new Date(); + yesterdayDate.setDate(yesterdayDate.getDate() - 1); + const yesterdayStr = getLocalDayString(yesterdayDate); + + const isDoneToday = uniqueDates.includes(todayStr); + const lastLoggedDate = uniqueDates[0]; + + // Calculate Current Streak + let currentStreak = 0; + + // Start checking from today (if done) or the last log date (if not done) + let checkDateStr = isDoneToday ? todayStr : uniqueDates[0]; + + // If not done today, check if the streak is already broken + if (!isDoneToday) { + let gapDate = new Date(); + gapDate.setDate(gapDate.getDate() - 1); + + // Move back through weekends if flexibility is on + while (weekendFlexibility && isWeekend(getLocalDayString(gapDate))) { + gapDate.setDate(gapDate.getDate() - 1); + } + + const latestAllowedDate = getLocalDayString(gapDate); + if (uniqueDates[0] < latestAllowedDate) { + // Streak is broken + currentStreak = 0; + checkDateStr = null; + } + } + + if (checkDateStr) { + let curr = new Date(); // Start from today + // If we're starting from a previous log, sync curr to that + if (!isDoneToday) { + const [y, m, d] = uniqueDates[0].split('-').map(Number); + curr = new Date(y, m - 1, d); + } + + let logPtr = 0; + // Fast forward logPtr to match checkDateStr + while (logPtr < uniqueDates.length && uniqueDates[logPtr] > checkDateStr) { + logPtr++; + } + + while (logPtr < uniqueDates.length) { + const currDateStr = getLocalDayString(curr); + const logDateStr = uniqueDates[logPtr]; + + if (logDateStr === currDateStr) { + currentStreak++; + logPtr++; + curr.setDate(curr.getDate() - 1); + } else if (weekendFlexibility && isWeekend(currDateStr)) { + curr.setDate(curr.getDate() - 1); + } else { + break; + } + } + } + + // Calculate Max Streak + let maxStreak = 0; + let tempStreak = 0; + + // To calculate max streak, we iterate from the earliest log to the latest + const earliestDateParts = uniqueDates[uniqueDates.length - 1].split('-').map(Number); + let curr = new Date(earliestDateParts[0], earliestDateParts[1] - 1, earliestDateParts[2]); + + const latestDateParts = uniqueDates[0].split('-').map(Number); + const lastDate = new Date(latestDateParts[0], latestDateParts[1] - 1, latestDateParts[2]); + + let logPtr = uniqueDates.length - 1; + while (curr <= lastDate) { + const currDateStr = getLocalDayString(curr); + const logDateStr = uniqueDates[logPtr]; + + if (logDateStr === currDateStr) { + tempStreak++; + logPtr--; + maxStreak = Math.max(maxStreak, tempStreak); + curr.setDate(curr.getDate() + 1); + } else if (weekendFlexibility && isWeekend(currDateStr)) { + curr.setDate(curr.getDate() + 1); + } else { + tempStreak = 0; + curr.setDate(curr.getDate() + 1); + } + } + + return { + currentStreak, + maxStreak, + isDoneToday, + lastLoggedDate + }; +} + +/** + * Updates the streak counts for a specific habit in the database + */ +export async function updateHabitStreak(habitId: string) { + const db = await getDb(); + + const habit = await db.getFirstAsync<{weekend_flexibility: number}>( + 'SELECT weekend_flexibility FROM habits WHERE id = ?', + [habitId] + ); + if (!habit) return; + + const logs = await db.getAllAsync<{logged_at: string}>( + 'SELECT logged_at FROM logs WHERE habit_id = ? ORDER BY logged_at DESC', + [habitId] + ); + + const streakInfo = calculateStreak( + logs.map(l => l.logged_at), + habit.weekend_flexibility === 1 + ); + + await db.runAsync( + 'UPDATE habits SET current_streak = ?, max_streak = ?, updated_at = ? WHERE id = ?', + [streakInfo.currentStreak, streakInfo.maxStreak, new Date().toISOString(), habitId] + ); +} + +/** + * Recalculates streaks for all active habits + */ +export async function recalculateAllStreaks(userId: string = 'guest') { + const db = await getDb(); + const habits = await db.getAllAsync<{id: string}>( + 'SELECT id FROM habits WHERE is_active = 1 AND (user_id = ? OR user_id IS NULL)', + [userId] + ); + + for (const habit of habits) { + await updateHabitStreak(habit.id); + } +} diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..459bdd2 --- /dev/null +++ b/src/lib/supabase.ts @@ -0,0 +1,18 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = + process.env.EXPO_PUBLIC_SUPABASE_URL || + "https://xhkkjelhmvepsouuibvs.supabase.co"; +const supabaseAnonKey = + process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || + "sb_publishable_g0QLc9OmyjBI1-mtUvz8Gg_qK8DG-Nl"; + +export const supabase = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + storage: AsyncStorage, + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: false, + }, +}); diff --git a/src/lib/sync.ts b/src/lib/sync.ts new file mode 100644 index 0000000..2528a20 --- /dev/null +++ b/src/lib/sync.ts @@ -0,0 +1,602 @@ +import { getDb } from "../db/database"; +import { updateHabitStreak } from "./habit-logic"; +import { supabase } from "./supabase"; + +export interface SyncOperation { + id?: number; + table_name: string; + operation: "INSERT" | "UPDATE" | "DELETE"; + payload: string; // JSON string + created_at?: string; +} + +let currentSyncPromise: Promise | null = null; +const databaseChangeListeners = new Set<() => void>(); + +export const subscribeToDatabaseChanges = (listener: () => void) => { + databaseChangeListeners.add(listener); + return () => databaseChangeListeners.delete(listener); +}; + +const emitDatabaseChange = () => { + for (const listener of databaseChangeListeners) { + try { + listener(); + } catch (err) { + console.error("Database change listener error:", err); + } + } +}; + +const isUUID = (str: string) => { + const regex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return regex.test(str); +}; + +const replaceIdsInPayload = (value: any, oldId: string, newId: string): any => { + if (value === oldId) { + return newId; + } + + if (Array.isArray(value)) { + return value.map((item) => replaceIdsInPayload(item, oldId, newId)); + } + + if (value && typeof value === "object") { + return Object.fromEntries( + Object.entries(value).map(([key, entry]) => [ + key, + replaceIdsInPayload(entry, oldId, newId), + ]), + ); + } + + return value; +}; + +export const addToSyncQueue = async ( + table_name: string, + operation: "INSERT" | "UPDATE" | "DELETE", + payload: any, +): Promise => { + const db = await getDb(); + const result = await db.runAsync( + "INSERT INTO sync_queue (table_name, operation, payload) VALUES (?, ?, ?)", + [table_name, operation, JSON.stringify(payload)], + ); + return result.lastInsertRowId; +}; + +/** + * Updates pending payloads in the sync queue when a temporary ID is replaced by a real UUID + */ +const updateQueuePayloads = async (oldId: string, newId: string) => { + const db = await getDb(); + const queue: SyncOperation[] = await db.getAllAsync( + "SELECT * FROM sync_queue", + ); + + for (const item of queue) { + try { + const parsedPayload = JSON.parse(item.payload); + const replacedPayload = replaceIdsInPayload(parsedPayload, oldId, newId); + const updatedPayload = JSON.stringify(replacedPayload); + if (updatedPayload !== item.payload) { + await db.runAsync("UPDATE sync_queue SET payload = ? WHERE id = ?", [ + updatedPayload, + item.id, + ]); + } + } catch { + // Keep the original payload if parsing fails + } + } +}; + +const migrateGuestDataToUser = async (userId: string) => { + const db = await getDb(); + const tablesWithUserId = ["habits", "schedules", "shortcuts", "tasks", "books", "reading_logs", "bookmarks", "logs"]; + + for (const table of tablesWithUserId) { + await db.runAsync(`UPDATE ${table} SET user_id = ? WHERE user_id = ?`, [ + userId, + "guest", + ]); + } + + const queue: SyncOperation[] = await db.getAllAsync( + "SELECT * FROM sync_queue", + ); + + for (const item of queue) { + if (item.payload.includes('"user_id":"guest"')) { + const updatedPayload = item.payload.replace( + /"user_id":"guest"/g, + `"user_id":"${userId}"`, + ); + if (updatedPayload !== item.payload) { + await db.runAsync("UPDATE sync_queue SET payload = ? WHERE id = ?", [ + updatedPayload, + item.id, + ]); + } + } + } +}; + +const hasPendingHabitInsert = (queue: SyncOperation[], habitId: string) => { + return queue.some((item) => { + if (item.table_name !== "habits" || item.operation !== "INSERT") { + return false; + } + try { + const payload = JSON.parse(item.payload); + return payload.id === habitId; + } catch { + return false; + } + }); +}; + +const processSyncItem = async (item: SyncOperation, user: any, db: any) => { + let payload: any; + try { + payload = JSON.parse(item.payload); + } catch (e) { + console.error(`Failed to parse payload for item ${item.id}:`, item.payload); + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + + const tablesWithUserId = ["habits", "schedules", "shortcuts", "tasks", "books", "reading_logs", "bookmarks", "logs", "sync_history"]; + + // HEAL: Strip 'last_page_read' from books if it exists (legacy column mismatch) + if (item.table_name === "books" && payload.hasOwnProperty("last_page_read")) { + const { last_page_read, ...cleanPayload } = payload; + payload = cleanPayload; + } + + // HEAL: Detect and fix missing columns in reading_logs on server + if (item.table_name === "reading_logs") { + // If the server doesn't have these yet, we might want to strip them + // but for now we'll let it fail and provide the SQL to the user. + // However, to keep sync moving for other items, we can add a check: + } + + // HEAL: Detect and fix "undefined" strings in UUID fields + if (payload.id === "undefined") { + console.warn(`Item ${item.id} (${item.table_name}) has "undefined" as ID. Abandoning.`); + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + + if (payload.user_id === "undefined") { + payload.user_id = user.id; + } + + // Handle foreign key relationships that might still be using temporary IDs + const fkFields: Record = { + 'logs': 'habit_id', + 'reading_logs': 'book_id', + 'bookmarks': 'book_id', + 'habits': 'anchor_habit_id' + }; + + const fkField = fkFields[item.table_name]; + if (fkField && payload[fkField]) { + const fkValue = payload[fkField]; + + if (fkValue === 'focus-session' && item.table_name === 'logs') { + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + + if (fkValue === 'undefined') { + console.warn(`Item ${item.id} (${item.table_name}) has "undefined" as ${fkField}. Abandoning.`); + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + + if (!isUUID(fkValue)) { + // Try to find the mapped UUID in sync_history or local table + const history = await db.getFirstAsync<{new_id: string}>( + "SELECT new_id FROM sync_history WHERE old_id = ?", + [fkValue] + ); + + if (history && isUUID(history.new_id)) { + payload[fkField] = history.new_id; + await db.runAsync("UPDATE sync_queue SET payload = ? WHERE id = ?", [JSON.stringify(payload), item.id]); + } else { + // Threshold check for orphaned items + const createdTime = item.created_at ? new Date(item.created_at).getTime() : Date.now(); + const now = Date.now(); + if (now - createdTime > 30 * 60 * 1000) { + console.warn(`Abandoning orphaned ${item.table_name} item ${item.id}: ${fkField} "${fkValue}" is not a UUID after 30m.`); + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + + console.warn(`Postponing sync for ${item.table_name} ${item.id}: ${fkField} "${fkValue}" is still a temporary ID.`); + return false; + } + } + } + + let error; + let remoteData; + + switch (item.operation) { + case "INSERT": { + const { id: localId, ...insertPayload } = payload; + + if (tablesWithUserId.includes(item.table_name)) { + if (insertPayload.user_id === "guest" || !insertPayload.user_id || insertPayload.user_id === "undefined") { + insertPayload.user_id = user.id; + } + } + + const { data: insertedData, error: insertError } = await supabase + .from(item.table_name) + .insert(insertPayload) + .select() + .single(); + + error = insertError; + remoteData = insertedData; + + if (!error && remoteData) { + await db.withTransactionAsync(async () => { + // Special case for habits/logs relationship + if (item.table_name === "habits") { + await db.runAsync( + "UPDATE logs SET habit_id = ? WHERE habit_id = ?", + [remoteData.id, localId], + ); + } + + if (tablesWithUserId.includes(item.table_name)) { + await db.runAsync( + `UPDATE ${item.table_name} SET id = ?, user_id = ? WHERE id = ?`, + [remoteData.id, user.id, localId], + ); + } else { + await db.runAsync( + `UPDATE ${item.table_name} SET id = ? WHERE id = ?`, + [remoteData.id, localId], + ); + } + + // Record ID mapping in sync_history + await db.runAsync( + "INSERT OR REPLACE INTO sync_history (old_id, new_id, table_name) VALUES (?, ?, ?)", + [localId, remoteData.id, item.table_name] + ); + + await updateQueuePayloads(localId, remoteData.id); + }); + + emitDatabaseChange(); + } + break; + } + + case "UPDATE": { + if ( + tablesWithUserId.includes(item.table_name) && + (payload.user_id === "guest" || payload.user_id === "undefined") + ) { + payload.user_id = user.id; + } + + // Final check: if ID is still not a UUID for an update, it's doomed unless we find it in history + if (!isUUID(payload.id)) { + const history = await db.getFirstAsync<{new_id: string}>("SELECT new_id FROM sync_history WHERE old_id = ?", [payload.id]); + if (history && isUUID(history.new_id)) { + payload.id = history.new_id; + } else { + console.error(`Cannot UPDATE ${item.table_name}: ID ${payload.id} is not a UUID and no mapping found.`); + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + } + + ({ error } = await supabase + .from(item.table_name) + .update(payload) + .eq("id", payload.id)); + break; + } + + case "DELETE": { + if (!isUUID(payload.id)) { + const history = await db.getFirstAsync<{new_id: string}>("SELECT new_id FROM sync_history WHERE old_id = ?", [payload.id]); + if (history && isUUID(history.new_id)) { + payload.id = history.new_id; + } else { + // If we can't find the remote ID, it probably never synced, so just delete locally + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + return true; + } + } + ({ error } = await supabase + .from(item.table_name) + .delete() + .eq("id", payload.id)); + break; + } + } + + if (!error) { + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + emitDatabaseChange(); + } else { + console.error( + `Sync error for item ${item.id} (${item.table_name}):`, + error.message, + "Payload:", JSON.stringify(payload) + ); + if ( + error.code === "42P01" || + error.code === "23503" || + error.code === "22P02" || + error.message.includes("row-level security") || + error.message.includes("uuid") + ) { + await db.runAsync("DELETE FROM sync_queue WHERE id = ?", [item.id]); + emitDatabaseChange(); + } + } + + return true; +}; + +export const syncWithSupabase = async () => { + if (currentSyncPromise) { + return currentSyncPromise; + } + + currentSyncPromise = (async () => { + const db = await getDb(); + const queue: SyncOperation[] = await db.getAllAsync( + "SELECT * FROM sync_queue ORDER BY id ASC", + ); + + const { + data: { user }, + } = await supabase.auth.getUser(); + + if (!user) { + if (queue.length === 0) { + await pullFromServer(); + } + return; + } + + await migrateGuestDataToUser(user.id); + + if (queue.length === 0) { + await pullFromServer(); + return; + } + + let remainingItems = queue; + let iteration = 0; + + while (remainingItems.length > 0 && iteration < 4) { + const nextItems: SyncOperation[] = []; + let anyAttempted = false; + + for (const item of remainingItems) { + const currentItem = await db.getFirstAsync( + "SELECT * FROM sync_queue WHERE id = ?", + [item.id], + ); + if (!currentItem) { + anyAttempted = true; + continue; + } + + const payload = JSON.parse(currentItem.payload); + + if ( + item.table_name === "logs" && + payload.habit_id && + !isUUID(payload.habit_id) && + hasPendingHabitInsert(remainingItems, payload.habit_id) + ) { + nextItems.push(item); + continue; + } + + const attempted = await processSyncItem(currentItem, user, db); + if (attempted) { + anyAttempted = true; + } else { + nextItems.push(item); + } + } + + if (!anyAttempted) { + break; + } + + remainingItems = nextItems; + iteration += 1; + } + })().finally(() => { + currentSyncPromise = null; + }); + + return currentSyncPromise; +}; + +export const pullFromServer = async () => { + const { + data: { user }, + } = await supabase.auth.getUser(); + if (!user) return; + + const db = await getDb(); + + // Get last sync time + const lastSyncResult = await db.getFirstAsync<{ value: string }>( + "SELECT value FROM settings WHERE key = 'last_pulled_at'", + ); + const lastPulledAt = lastSyncResult?.value; + + const tables = ["habits", "schedules", "logs", "shortcuts", "tasks", "books", "reading_logs", "bookmarks", "sync_history"]; + + for (const table of tables) { + try { + let syncTouchedLocalRows = false; + let query = supabase.from(table).select("*"); + + const tablesWithUserId = ["habits", "schedules", "shortcuts", "tasks", "books", "reading_logs", "bookmarks", "logs", "sync_history"]; + if (tablesWithUserId.includes(table)) { + query = query.eq("user_id", user.id); + } + + // Incremental sync logic + if (lastPulledAt) { + const timeColumn = table === "logs" ? "created_at" : "updated_at"; + query = query.gt(timeColumn, lastPulledAt); + } + + const { data, error } = await query; + + if (!error && data) { + for (const remoteItem of data) { + const localItem = await db.getFirstAsync( + `SELECT id FROM ${table} WHERE id = ?`, + [remoteItem.id], + ); + + if (localItem) { + const keys = Object.keys(remoteItem).filter((k) => k !== "id"); + const setClause = keys.map((k) => `${k} = ?`).join(","); + const values = [ + ...keys.map((k) => { + const val = remoteItem[k]; + return typeof val === "object" ? JSON.stringify(val) : val; + }), + remoteItem.id, + ]; + + await db.runAsync( + `UPDATE ${table} SET ${setClause} WHERE id = ?`, + values as any, + ); + syncTouchedLocalRows = true; + } else { + const keys = Object.keys(remoteItem); + const placeholders = keys.map(() => "?").join(","); + const values = keys.map((k) => { + const val = remoteItem[k]; + return typeof val === "object" ? JSON.stringify(val) : val; + }); + + await db.runAsync( + `INSERT OR IGNORE INTO ${table} (${keys.join(",")}) VALUES (${placeholders})`, + values as any, + ); + syncTouchedLocalRows = true; + } + } + } + + if (syncTouchedLocalRows) { + emitDatabaseChange(); + } + } catch (err) { + console.error(`Failed to pull table ${table}:`, err); + } + } + + // Update last sync time + const now = new Date().toISOString(); + await db.runAsync( + "INSERT OR REPLACE INTO settings (key, value) VALUES ('last_pulled_at', ?)", + [now], + ); +}; + +export const performMutation = async ( + table_name: string, + operation: "INSERT" | "UPDATE" | "DELETE", + payload: any, +) => { + console.log("performMutation START", { table_name, operation, payload }); + + if (!payload || (typeof payload === 'object' && Object.keys(payload).length === 0)) { + console.warn("performMutation: empty payload, skipping execution.", { table_name, operation }); + return undefined; + } + + const db = await getDb(); + + switch (operation) { + case "INSERT": + const keys = Object.keys(payload).join(","); + const placeholders = Object.keys(payload) + .map(() => "?") + .join(","); + const values = Object.values(payload).map((v) => + typeof v === "object" ? JSON.stringify(v) : v, + ); + await db.runAsync( + `INSERT INTO ${table_name} (${keys}) VALUES (${placeholders})`, + values as any, + ); + break; + case "UPDATE": + const updateKeys = Object.keys(payload).filter((k) => k !== "id"); + if (updateKeys.length === 0) { + console.warn("performMutation: nothing to update except ID, skipping.", { table_name }); + return undefined; + } + const setClause = updateKeys + .map((k) => `${k} = ?`) + .join(","); + const updateValues = [ + ...updateKeys.map((k) => { + const v = payload[k]; + return typeof v === "object" ? JSON.stringify(v) : v; + }), + payload.id, + ]; + await db.runAsync( + `UPDATE ${table_name} SET ${setClause} WHERE id = ?`, + updateValues as any, + ); + break; + case "DELETE": + if (!payload.id) { + console.error("performMutation: DELETE missing ID.", { table_name }); + return undefined; + } + await db.runAsync(`DELETE FROM ${table_name} WHERE id = ?`, [payload.id]); + break; + } + + emitDatabaseChange(); + + // Handle streak maintenance + if (table_name === "logs") { + if (payload.habit_id) { + await updateHabitStreak(payload.habit_id); + } + } else if ( + table_name === "habits" && + (operation === "UPDATE" || operation === "INSERT") + ) { + // If weekend_flexibility changed or new habit added, recalculate/init + await updateHabitStreak(payload.id); + } + + const syncId = await addToSyncQueue(table_name, operation, payload); + console.log("performMutation RESULT", { success: !!syncId, syncId }); + syncWithSupabase().catch(console.error); + return syncId; +}; diff --git a/src/lib/sync.web.ts b/src/lib/sync.web.ts new file mode 100644 index 0000000..99a216e --- /dev/null +++ b/src/lib/sync.web.ts @@ -0,0 +1,35 @@ + +export interface SyncOperation { + id?: number; + table_name: string; + operation: "INSERT" | "UPDATE" | "DELETE"; + payload: string; + created_at?: string; +} + +export const addToSyncQueue = async ( + table_name: string, + operation: "INSERT" | "UPDATE" | "DELETE", + payload: any, +) => { + console.warn("Sync queue is disabled on web."); +}; + +export const syncWithSupabase = async () => { + console.warn("Sync is disabled on web."); +}; + +export const performMutation = async ( + table_name: string, + operation: "INSERT" | "UPDATE" | "DELETE", + payload: any, +) => { + console.warn("Local mutation sync is disabled on web."); + await addToSyncQueue(table_name, operation, payload); + await syncWithSupabase(); +}; + +export const subscribeToDatabaseChanges = (callback: () => void) => { + // No-op on web for now as local database isn't persistent + return () => {}; +}; diff --git a/src/screens/AIScreen.tsx b/src/screens/AIScreen.tsx new file mode 100644 index 0000000..314ce3f --- /dev/null +++ b/src/screens/AIScreen.tsx @@ -0,0 +1,286 @@ +import ChatHeader from "@/src/components/chat/ChatHeader"; +import ChatInput from "@/src/components/chat/ChatInput"; +import MessageBubble from "@/src/components/chat/MessageBubble"; +import SuggestionChips from "@/src/components/chat/SuggestionChips"; +import { ErrorBoundary } from "@/src/components/ErrorBoundary"; +import { FONTS, ROUNDNESS, SPACING, ThemeColors } from "@/src/constants/Theme"; +import { useAIChat } from "@/src/hooks/useAIChat"; +import { useAuth } from "@/src/hooks/useAuth"; +import { useChatHistory } from "@/src/hooks/useChatHistory"; +import { useHabitSummary } from "@/src/hooks/useHabitSummary"; +import { useRequireAuth } from "@/src/hooks/useRequireAuth"; +import { useTheme } from "@/src/hooks/useTheme"; +import { ChatMessage } from "@/src/types/chat"; +import * as Haptics from "expo-haptics"; +import { useRouter } from "expo-router"; +import { Sparkles } from "lucide-react-native"; +import React, { + useCallback, + useEffect, + useRef, + useState +} from "react"; +import { + ActivityIndicator, + FlatList, + InteractionManager, + KeyboardAvoidingView, + Platform, + SafeAreaView, + StyleSheet, + Text, + View, +} from "react-native"; + +const INITIAL_SUGGESTIONS = [ + "Audit my habits", + "Suggest a 2-min version", + "Log my workout", + "Help me build a morning routine", +] as const; + +interface AIScreenProps { + colors: ThemeColors; +} + +const AIScreen = () => { + const { colors, identityAnchor } = useTheme(); + const { user, isAuthenticated, loading: authLoading } = useAuth(); + useRequireAuth(user, authLoading); + + const router = useRouter(); + const [inputText, setInputText] = useState(""); + + const userId = user?.id ?? null; + const { habitContext } = useHabitSummary(userId ?? "guest"); + const { + messages, + messagesRef, + isLoading: isHistoryLoading, + appendMessage, + updateMessage, + } = useChatHistory(userId); + + const { isSending, sendMessage } = useAIChat({ + userId, + identityAnchor, + habitContext, + messagesRef, + appendMessage, + updateMessage, + }); + + const listRef = useRef>(null); + const isUserScrolling = useRef(false); + + const staticStyles = styles; + + const scrollToBottom = useCallback(() => { + if (isUserScrolling.current) return; + InteractionManager.runAfterInteractions(() => { + listRef.current?.scrollToOffset({ offset: 0, animated: true }); + }); + }, []); + + useEffect(() => { + if (messages.length > 0) { + scrollToBottom(); + } + }, [messages.length, scrollToBottom]); + + const handleSend = useCallback( + (text: string) => { + const content = text.trim(); + if (!content) return; + setInputText(""); + sendMessage(content); + }, + [sendMessage], + ); + + const navigateMenu = useCallback(() => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/menu"); + }, [router]); + + const navigateSettings = useCallback(() => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/modal"); + }, [router]); + + const onSuggestionPress = useCallback( + (suggestion: string) => { + handleSend(suggestion); + }, + [handleSend], + ); + + const renderMessage = useCallback( + ({ item }: { item: ChatMessage }) => ( + + ), + [colors], + ); + + const keyExtractor = useCallback((item: ChatMessage) => item.id, []); + + const onScroll = useCallback(({ nativeEvent }) => { + isUserScrolling.current = nativeEvent.contentOffset.y > 0; + }, []); + + if (authLoading || (!isAuthenticated && !user)) { + return ( + + + + ); + } + + return ( + + + + + + + + + + Architect Mode Active + + + + {isHistoryLoading ? ( + + + + Retrieving conversation... + + + ) : ( + + } + /> + )} + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + safeArea: { + flex: 1, + }, + keyboardView: { + flex: 1, + }, + chatContentWrapper: { + flex: 1, + }, + chatContainer: { + padding: SPACING.lg, + paddingBottom: 20, + }, + assistantStatus: { + flexDirection: "row", + alignItems: "center", + gap: 8, + marginBottom: SPACING.xl, + justifyContent: "center", + backgroundColor: "rgba(52, 144, 220, 0.1)", + paddingVertical: 6, + paddingHorizontal: 16, + borderRadius: ROUNDNESS.full, + alignSelf: "center", + }, + statusText: { + fontFamily: FONTS.labelSm, + fontSize: 11, + }, + historyLoading: { + alignItems: "center", + justifyContent: "center", + paddingVertical: 40, + gap: 12, + }, + historyLoadingText: { + fontFamily: FONTS.label, + fontSize: 13, + }, + flatListFooter: { + height: 20, + }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, +}); + +export default AIScreen; diff --git a/src/services/aiService.ts b/src/services/aiService.ts new file mode 100644 index 0000000..4f16f5c --- /dev/null +++ b/src/services/aiService.ts @@ -0,0 +1,47 @@ +import { supabase } from "@/src/lib/supabase"; +import { ChatRole } from "@/src/types/chat"; + +export interface AiAssistantResponse { + reply?: string; + error?: string; +} + +export const buildSystemPrompt = ( + identityAnchor: string, + habitContext: string, +) => + `You are Batsirai, a Habit Architect and productivity coach. +Your tone is encouraging, professional, and insightful. +User Profile: I am ${identityAnchor}. +User Progress: ${habitContext} + +Always provide actionable advice based on Atomic Habits principles (e.g., 2-minute rule, habit stacking).`; + +export const callAiAssistant = async ( + messages: Array<{ role: ChatRole; content: string }>, + systemPrompt: string, + onToken?: (token: string) => void, +): Promise => { + try { + const { data, error } = await supabase.functions.invoke("chat-ai", { + body: { messages, systemPrompt }, + }); + + if (error) { + console.error("Supabase Function Error:", error); + throw error; + } + + if (typeof data === "object" && data !== null) { + return data as AiAssistantResponse; + } + + return { error: "Invalid AI response format." }; + } catch (err) { + console.error("AI Assistant Error:", err); + if (onToken) { + onToken(String(err)); + } + throw err; + } +}; diff --git a/src/services/chatService.ts b/src/services/chatService.ts new file mode 100644 index 0000000..0896c02 --- /dev/null +++ b/src/services/chatService.ts @@ -0,0 +1,85 @@ +import { supabase } from "@/src/lib/supabase"; +import { ChatMessage } from "@/src/types/chat"; + +const createUniqueId = () => + `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; + +export const createUserMessage = (content: string): ChatMessage => ({ + id: createUniqueId(), + role: "user", + content, + createdAt: Date.now(), + status: "sent", +}); + +export const createAssistantMessage = ( + content: string, + status: ChatMessage["status"] = "sending", +): ChatMessage => ({ + id: createUniqueId(), + role: "assistant", + content, + createdAt: Date.now(), + status, +}); + +export const createGreetingMessage = (): ChatMessage => ({ + id: "welcome-architect", + role: "assistant", + content: + "I'm Batsirai, your Habit Architect. I can help you build consistency, audit your schedule, or refine your 'Two-Minute' versions. How can we find your flow today?", + createdAt: Date.now(), + status: "sent", +}); + +export const mapChatRecordToMessage = (record: any): ChatMessage => ({ + id: record.id || createUniqueId(), + role: record.sender === "ai" ? "assistant" : "user", + content: record.text || "", + createdAt: record.created_at + ? new Date(record.created_at).getTime() + : Date.now(), + status: "sent", + action: record.action, +}); + +export const fetchChatHistory = async ( + userId: string, +): Promise => { + const { data, error } = await supabase + .from("chat_messages") + .select("*") + .eq("user_id", userId) + .order("created_at", { ascending: false }); + + if (error) { + throw error; + } + + return (data ?? []).map(mapChatRecordToMessage); +}; + +export const saveChatMessage = async ( + userId: string, + message: ChatMessage, +): Promise => { + const sender = message.role === "assistant" ? "ai" : "user"; + const payload = { + user_id: userId, + sender, + text: message.content, + created_at: new Date(message.createdAt).toISOString(), + }; + + const { error } = await supabase.from("chat_messages").insert(payload); + + if (error) { + console.warn("Failed to persist chat message", error); + throw error; + } +}; + +export const serializeHistoryMessage = (message: ChatMessage) => ({ + role: message.role, + content: message.content, +}); diff --git a/src/types/chat.ts b/src/types/chat.ts new file mode 100644 index 0000000..656a170 --- /dev/null +++ b/src/types/chat.ts @@ -0,0 +1,11 @@ +export type ChatRole = "user" | "assistant"; +export type ChatStatus = "sending" | "sent" | "error" | "streaming"; + +export interface ChatMessage { + id: string; + role: ChatRole; + content: string; + createdAt: number; + status?: ChatStatus; + action?: string; +} diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts new file mode 100644 index 0000000..f23e354 --- /dev/null +++ b/src/utils/formatTime.ts @@ -0,0 +1,5 @@ +export const formatTime = (value: number | Date = new Date()) => + new Date(value).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); diff --git a/supabase/functions/chat-ai/index.ts b/supabase/functions/chat-ai/index.ts new file mode 100644 index 0000000..729e8f1 --- /dev/null +++ b/supabase/functions/chat-ai/index.ts @@ -0,0 +1,121 @@ +import { createClient } from "https://esm.sh/@supabase/supabase-js@2.43.1"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": + "authorization, apikey, content-type, x-client-info", + "Access-Control-Allow-Methods": "POST, OPTIONS", +}; + +Deno.serve(async (req) => { + // 1. Handle CORS Preflight (OPTIONS request) - Must return 200 OK + if (req.method === "OPTIONS") { + return new Response("ok", { headers: corsHeaders, status: 200 }); + } + + try { + // 2. Validate API Key + const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") || Deno.env.get("VITE_GEMINI_API_KEY"); + if (!GEMINI_API_KEY) { + throw new Error("Missing GEMINI_API_KEY secret"); + } + + // 3. Initialize Supabase Client + const supabaseClient = createClient( + Deno.env.get("SUPABASE_URL") ?? "", + Deno.env.get("SUPABASE_ANON_KEY") ?? "", + { + global: { + headers: { Authorization: req.headers.get("Authorization")! }, + }, + } + ); + + // 4. Get User Info + const { + data: { user }, + } = await supabaseClient.auth.getUser(); + + if (!user) { + throw new Error("Unauthorized"); + } + + // 5. Parse Request + const { messages, systemPrompt } = await req.json(); + + if (!messages || !Array.isArray(messages)) { + throw new Error("Invalid body: 'messages' array is required"); + } + + // 6. Save User Message (the last one in the incoming list) + const userMessage = messages[messages.length - 1]; + if (userMessage && userMessage.sender === "user") { + await supabaseClient.from("chat_messages").insert({ + user_id: user.id, + text: userMessage.text, + sender: "user", + }); + } + + // 7. Build Prompt + let prompt = systemPrompt ? `${systemPrompt}\n\n` : ""; + prompt += messages + .map((m: any) => + m.sender === "user" ? `User: ${m.text}` : `Assistant: ${m.text}` + ) + .join("\n"); + prompt += "\nAssistant:"; + + // 8. Call Gemini API + const model = "gemini-2.5-flash"; + + const geminiRes = await fetch( + `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=` + + GEMINI_API_KEY, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + contents: [{ role: "user", parts: [{ text: prompt }] }], + generationConfig: { + temperature: 0.6, + maxOutputTokens: 1024, + }, + }), + } + ); + + // 9. Handle Gemini Errors + if (!geminiRes.ok) { + const errorData = await geminiRes.json(); + console.error("Gemini API Error:", errorData); + throw new Error(errorData.error?.message || "Gemini API error"); + } + + const data = await geminiRes.json(); + const reply = + data?.candidates?.[0]?.content?.parts?.[0]?.text || + "I'm here to help!"; + + // 10. Save AI Reply + await supabaseClient.from("chat_messages").insert({ + user_id: user.id, + text: reply, + sender: "ai", + }); + + // 11. Success Response + return new Response(JSON.stringify({ reply }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + status: 200, + }); + + } catch (error: any) { + // 12. Catch-all Error Handling + console.error("Edge Function Error:", error.message); + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + status: error.message === "Unauthorized" ? 401 : 500, + }); + } +}); diff --git a/supabase/functions/process-book-ai/index.ts b/supabase/functions/process-book-ai/index.ts new file mode 100644 index 0000000..4a59ea5 --- /dev/null +++ b/supabase/functions/process-book-ai/index.ts @@ -0,0 +1,50 @@ +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; + +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY"); + +serve(async (req) => { + if (req.method === "OPTIONS") { + return new Response("ok", { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Headers": "*" } }); + } + + try { + const { pdf_base64, filename } = await req.json(); + + if (!GEMINI_API_KEY) { + return new Response(JSON.stringify({ error: "API key not configured" }), { status: 500 }); + } + + // Since we are in 2026, we use the multimodal capabilities of Gemini 3.1 Flash. + // We send a snippet of the base64 data (the first few KB usually contain header/metadata). + // For a production app, we'd use a dedicated PDF parsing library in the edge function, + // but here we'll use Gemini's reasoning on the filename and context. + + const prompt = `I am uploading a file named "${filename}". + Please extract the formal Book Title and the Author's full name. + Also, based on common knowledge of this book, what is its approximate total page count? + + Return ONLY a JSON object: + {"title": "string", "author": "string", "totalPages": number}`; + + const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash:generateContent?key=${GEMINI_API_KEY}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + contents: [{ parts: [{ text: prompt }] }] + }) + }); + + const result = await response.json(); + const text = result.candidates?.[0]?.content?.parts?.[0]?.text || "{}"; + + // Clean up potential markdown formatting in the response + const jsonString = text.replace(/```json|```/g, "").trim(); + const metadata = JSON.parse(jsonString); + + return new Response(JSON.stringify(metadata), { + headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { status: 400 }); + } +}); diff --git a/supabase/schema.sql b/supabase/schema.sql new file mode 100644 index 0000000..5ac26eb --- /dev/null +++ b/supabase/schema.sql @@ -0,0 +1,407 @@ +-- Supabase Schema Initialization for Batsir Productivity Planner +-- Idempotent version - safe to run multiple times without conflicts + +-- Enable extensions +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +---------------------------------------------------- +-- 1. Tables Setup (Idempotent - IF NOT EXISTS) +---------------------------------------------------- + +-- Core tables +CREATE TABLE IF NOT EXISTS public.profiles ( + id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY, + email TEXT, + display_name TEXT, + preferences JSONB DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.habits ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + title TEXT NOT NULL, + frequency TEXT DEFAULT 'daily', + preferred_time TIME, + location TEXT, + two_minute_version TEXT, + anchor_habit_id UUID REFERENCES public.habits(id) ON DELETE SET NULL, + weekend_flexibility BOOLEAN DEFAULT FALSE, + is_active BOOLEAN DEFAULT TRUE, + current_streak INTEGER DEFAULT 0, + max_streak INTEGER DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.schedules ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + date DATE NOT NULL, + time_blocks JSONB DEFAULT '[]'::jsonb, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + habit_id UUID REFERENCES public.habits(id) ON DELETE CASCADE NOT NULL, + status TEXT, + logged_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.sync_queue ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + table_name TEXT, + operation TEXT, + payload JSONB, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.shortcuts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + title TEXT, + url TEXT, + icon TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.tasks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + title TEXT, + status TEXT DEFAULT 'todo', + estimated_sessions INTEGER DEFAULT 1, + completed_sessions INTEGER DEFAULT 0, + tag TEXT, + todos JSONB DEFAULT '[]'::jsonb, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.books ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + title TEXT NOT NULL, + author TEXT, + total_pages INTEGER DEFAULT 0, + current_page INTEGER DEFAULT 0, + last_page_read INTEGER DEFAULT 0, + file_uri TEXT, + cover_uri TEXT, + status TEXT DEFAULT 'want_to_read', + synthesis TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.reading_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + book_id UUID REFERENCES public.books(id) ON DELETE CASCADE NOT NULL, + pages_read INTEGER DEFAULT 0, + duration_minutes DOUBLE PRECISION DEFAULT 0, + duration_seconds DOUBLE PRECISION DEFAULT 0, + logged_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.bookmarks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + book_id UUID REFERENCES public.books(id) ON DELETE CASCADE NOT NULL, + page_number INTEGER NOT NULL, + note TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS public.sync_history ( + old_id TEXT NOT NULL, + new_id UUID NOT NULL, + table_name TEXT NOT NULL, + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + synced_at TIMESTAMPTZ DEFAULT NOW(), + PRIMARY KEY (old_id, table_name, user_id) +); + +CREATE TABLE IF NOT EXISTS public.chat_messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL, + text TEXT NOT NULL, + sender TEXT NOT NULL CHECK (sender IN ('user', 'ai')), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +---------------------------------------------------- +-- 2. Safe Column Migrations (Check before adding) +---------------------------------------------------- + +DO $$ +BEGIN + -- Books table migrations + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'books' AND column_name = 'synthesis') THEN + ALTER TABLE public.books ADD COLUMN synthesis TEXT; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'books' AND column_name = 'last_page_read') THEN + ALTER TABLE public.books ADD COLUMN last_page_read INTEGER DEFAULT 0; + END IF; + + -- Reading logs migrations + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'reading_logs' AND column_name = 'duration_seconds') THEN + ALTER TABLE public.reading_logs ADD COLUMN duration_seconds DOUBLE PRECISION DEFAULT 0; + END IF; + + -- Alter column type safely (only if needed) + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'reading_logs' AND column_name = 'duration_minutes' AND data_type = 'integer' + ) THEN + ALTER TABLE public.reading_logs ALTER COLUMN duration_minutes TYPE DOUBLE PRECISION; + END IF; +END $$; + +---------------------------------------------------- +-- 3. RLS Enablement (Idempotent) +---------------------------------------------------- + +DO $$ +DECLARE + tables text[] := ARRAY['profiles', 'habits', 'schedules', 'logs', 'sync_queue', 'shortcuts', 'tasks', 'books', 'reading_logs', 'bookmarks', 'sync_history', 'chat_messages']; + tbl text; +BEGIN + FOREACH tbl IN ARRAY tables + LOOP + EXECUTE format('ALTER TABLE IF EXISTS public.%I ENABLE ROW LEVEL SECURITY', tbl); + END LOOP; +END $$; + +---------------------------------------------------- +-- 4. Safe Policy Recreation (Drop only existing policies) +---------------------------------------------------- + +DO $$ +DECLARE + pol RECORD; +BEGIN + FOR pol IN ( + SELECT policyname, tablename + FROM pg_policies + WHERE schemaname = 'public' + ) + LOOP + BEGIN + EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', pol.policyname, pol.tablename); + EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE 'Could not drop policy % on table %: %', pol.policyname, pol.tablename, SQLERRM; + END; + END LOOP; +END $$; + +-- Profiles (Link is 'id' to auth.uid()) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Profiles: Users view own' AND tablename = 'profiles') THEN + CREATE POLICY "Profiles: Users view own" ON public.profiles FOR SELECT USING (auth.uid() = id); + CREATE POLICY "Profiles: Users update own" ON public.profiles FOR UPDATE USING (auth.uid() = id); + CREATE POLICY "Profiles: Users insert own" ON public.profiles FOR INSERT WITH CHECK (auth.uid() = id); + END IF; +END $$; + +-- Repeat for each table (only create if not exists) +DO $$ +BEGIN + -- Habits policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Habits: Users view own' AND tablename = 'habits') THEN + CREATE POLICY "Habits: Users view own" ON public.habits FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Habits: Users insert own" ON public.habits FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Habits: Users update own" ON public.habits FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Habits: Users delete own" ON public.habits FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Schedules policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Schedules: Users view own' AND tablename = 'schedules') THEN + CREATE POLICY "Schedules: Users view own" ON public.schedules FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Schedules: Users insert own" ON public.schedules FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Schedules: Users update own" ON public.schedules FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Schedules: Users delete own" ON public.schedules FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Logs policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Logs: Users view own' AND tablename = 'logs') THEN + CREATE POLICY "Logs: Users view own" ON public.logs FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Logs: Users insert own" ON public.logs FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Logs: Users update own" ON public.logs FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Logs: Users delete own" ON public.logs FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Sync Queue policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'SyncQueue: Users view own' AND tablename = 'sync_queue') THEN + CREATE POLICY "SyncQueue: Users view own" ON public.sync_queue FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "SyncQueue: Users insert own" ON public.sync_queue FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "SyncQueue: Users delete own" ON public.sync_queue FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Shortcuts policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Shortcuts: Users view own' AND tablename = 'shortcuts') THEN + CREATE POLICY "Shortcuts: Users view own" ON public.shortcuts FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Shortcuts: Users insert own" ON public.shortcuts FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Shortcuts: Users update own" ON public.shortcuts FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Shortcuts: Users delete own" ON public.shortcuts FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Tasks policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Tasks: Users view own' AND tablename = 'tasks') THEN + CREATE POLICY "Tasks: Users view own" ON public.tasks FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Tasks: Users insert own" ON public.tasks FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Tasks: Users update own" ON public.tasks FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Tasks: Users delete own" ON public.tasks FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Books policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Books: Users view own' AND tablename = 'books') THEN + CREATE POLICY "Books: Users view own" ON public.books FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Books: Users insert own" ON public.books FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Books: Users update own" ON public.books FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Books: Users delete own" ON public.books FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Reading Logs policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'ReadingLogs: Users view own' AND tablename = 'reading_logs') THEN + CREATE POLICY "ReadingLogs: Users view own" ON public.reading_logs FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "ReadingLogs: Users insert own" ON public.reading_logs FOR INSERT WITH CHECK (auth.uid() = user_id); + END IF; + + -- Bookmarks policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Bookmarks: Users view own' AND tablename = 'bookmarks') THEN + CREATE POLICY "Bookmarks: Users view own" ON public.bookmarks FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "Bookmarks: Users insert own" ON public.bookmarks FOR INSERT WITH CHECK (auth.uid() = user_id); + CREATE POLICY "Bookmarks: Users update own" ON public.bookmarks FOR UPDATE USING (auth.uid() = user_id); + CREATE POLICY "Bookmarks: Users delete own" ON public.bookmarks FOR DELETE USING (auth.uid() = user_id); + END IF; + + -- Sync History policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'SyncHistory: Users view own' AND tablename = 'sync_history') THEN + CREATE POLICY "SyncHistory: Users view own" ON public.sync_history FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "SyncHistory: Users insert own" ON public.sync_history FOR INSERT WITH CHECK (auth.uid() = user_id); + END IF; + + -- Chat Messages policies + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'ChatMessages: Users view own' AND tablename = 'chat_messages') THEN + CREATE POLICY "ChatMessages: Users view own" ON public.chat_messages FOR SELECT USING (auth.uid() = user_id); + CREATE POLICY "ChatMessages: Users insert own" ON public.chat_messages FOR INSERT WITH CHECK (auth.uid() = user_id); + END IF; +END $$; + +---------------------------------------------------- +-- 5. Triggers & Functions (Safe create or replace) +---------------------------------------------------- + +-- Function for updated_at +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ language 'plpgsql'; + +-- Profile Handler +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, email) + VALUES (new.id, new.email) + ON CONFLICT (id) DO UPDATE + SET email = EXCLUDED.email; + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Drop and recreate triggers safely +DO $$ +DECLARE + trigger_config record; +BEGIN + -- Drop existing triggers + FOR trigger_config IN ( + SELECT tgname AS trigger_name, relname AS table_name + FROM pg_trigger + JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid + WHERE tgname IN ('update_profiles_updated_at', 'update_habits_updated_at', 'update_schedules_updated_at', + 'update_logs_updated_at', 'update_shortcuts_updated_at', 'update_tasks_updated_at', + 'update_books_updated_at', 'on_auth_user_created') + ) LOOP + BEGIN + EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.%I', trigger_config.trigger_name, trigger_config.table_name); + EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE 'Could not drop trigger %: %', trigger_config.trigger_name, SQLERRM; + END; + END LOOP; +END $$; + +-- Recreate triggers +CREATE TRIGGER update_profiles_updated_at BEFORE UPDATE ON public.profiles FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); +CREATE TRIGGER update_habits_updated_at BEFORE UPDATE ON public.habits FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); +CREATE TRIGGER update_schedules_updated_at BEFORE UPDATE ON public.schedules FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); +CREATE TRIGGER update_logs_updated_at BEFORE UPDATE ON public.logs FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); +CREATE TRIGGER update_shortcuts_updated_at BEFORE UPDATE ON public.shortcuts FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); +CREATE TRIGGER update_tasks_updated_at BEFORE UPDATE ON public.tasks FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); +CREATE TRIGGER update_books_updated_at BEFORE UPDATE ON public.books FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); + +-- Handle auth trigger separately to avoid duplicate +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; +CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user(); + +---------------------------------------------------- +-- 6. Storage Setup (Idempotent) +---------------------------------------------------- + +-- Insert bucket if not exists +INSERT INTO storage.buckets (id, name, public) +VALUES ('books', 'books', false) +ON CONFLICT (id) DO NOTHING; + +-- Create storage policies safely +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Books Bucket: Users upload own' AND schemaname = 'storage') THEN + CREATE POLICY "Books Bucket: Users upload own" ON storage.objects + FOR INSERT WITH CHECK (bucket_id = 'books' AND auth.uid() = owner); + + CREATE POLICY "Books Bucket: Users view own" ON storage.objects + FOR SELECT USING (bucket_id = 'books' AND auth.uid() = owner); + + CREATE POLICY "Books Bucket: Users delete own" ON storage.objects + FOR DELETE USING (bucket_id = 'books' AND auth.uid() = owner); + END IF; +END $$; + +---------------------------------------------------- +-- 7. Additional Conflict Prevention +---------------------------------------------------- + +-- Create indexes for better performance and conflict prevention +CREATE INDEX IF NOT EXISTS idx_habits_user_id ON public.habits(user_id); +CREATE INDEX IF NOT EXISTS idx_schedules_user_date ON public.schedules(user_id, date); +CREATE INDEX IF NOT EXISTS idx_logs_user_habit ON public.logs(user_id, habit_id); +CREATE INDEX IF NOT EXISTS idx_tasks_user_id ON public.tasks(user_id); +CREATE INDEX IF NOT EXISTS idx_books_user_id ON public.books(user_id); +CREATE INDEX IF NOT EXISTS idx_reading_logs_book_id ON public.reading_logs(book_id); +CREATE INDEX IF NOT EXISTS idx_bookmarks_book_id ON public.bookmarks(book_id); + +-- Add comments for documentation +COMMENT ON TABLE public.profiles IS 'User profiles with preferences'; +COMMENT ON TABLE public.habits IS 'User habits with streak tracking'; +COMMENT ON TABLE public.schedules IS 'Daily schedules with time blocks'; +COMMENT ON TABLE public.logs IS 'Habit completion logs'; +COMMENT ON TABLE public.tasks IS 'Task management'; +COMMENT ON TABLE public.books IS 'Book library with reading progress'; \ No newline at end of file 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" + ] +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..72798cb --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,27 @@ +const createExpoWebpackConfigAsync = require("@expo/webpack-config"); + +module.exports = async function (env, argv) { + const config = await createExpoWebpackConfigAsync(env, argv); + + config.resolve.alias = { + ...(config.resolve.alias || {}), + "lucide-react-native": "lucide-react", + }; + + config.resolve.extensions = [ + ...new Set([...(config.resolve.extensions || []), ".wasm"]), + ]; + + config.module.rules.push({ + test: /\.wasm$/, + type: "asset/resource", + }); + + config.experiments = { + ...(config.experiments || {}), + asyncWebAssembly: true, + syncWebAssembly: true, + }; + + return config; +};