commit cec967f9536eb772c69b92a0c82d7055ef6ea7a3 Author: Top Clossers Date: Fri Jul 17 12:33:59 2026 +0300 Livrare Lot 3 (Frontend): aplicație web, aplicație mobilă Android, extensie browser - Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3) - Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor - Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS - Configurare adresă platformă externalizată (build args / .env / config.js) Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1250b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +.expo/ +android/didi-app-source/android/app/build/ +android/didi-app-source/android/.gradle/ +android/didi-app-source/android/build/ +android/didi-app-source/.env +*.log +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed8905a --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# DiDi — Lot 3 (Frontend) · Livrare + +Platformă digitală inteligentă pentru prevenirea și combaterea dezinformării — **DiDi** +PNRR DIGI150 · Contract de finanțare 11.1.i3.c9 · Contract de furnizare nr. 19 + +Componentele frontend ale platformei DiDi: **aplicație web**, **aplicație mobilă (Android)** +și **extensie de browser (Chrome/Edge, Manifest V3)**, integrate cu backend-ul platformei +(Lot 1 — AI, Lot 2 — Backend) prin API Gateway (Kong) și IAM (Keycloak, OIDC/OAuth2 + PKCE). + +## Structura repo-ului + +| Cale | Conținut | +|---|---| +| `web/` | Aplicația web — React 18 + Vite + TypeScript (SPA), imagine nginx | +| `android/didi-app-source/` | Aplicația mobilă — React Native + Expo SDK 54 (include proiectul nativ `android/` cu patch-urile de manifest) | +| `extension/` | Extensia de browser — MV3, vanilla JS | +| `docs/` | Documentația de livrare: 05 Ghid de utilizare · 06 Matrice de trasabilitate a cerințelor · 07 Raport de testare furnizor (md + docx, cu capturi încorporate) | +| `artefacte/` | Livrabile binare: imagine Docker (tar.gz), APK, extensie (zip), `SHA256SUMS.txt`, `README_LIVRARE.md` | +| `docker-compose.local.yml` | Deployment-ul aplicației web pe serverul platformei | +| `build.sh`, `TESTING.md` | Utilitare de build / note de testare | + +## Configurarea adresei platformei + +Sursele nu conțin adrese hardcodate. Adresa serverului DiDi se setează per componentă: + +1. **Web** — la build: `docker build --build-arg VITE_API_URL= --build-arg VITE_KEYCLOAK_URL=https:///auth -t didi-frontend:local ./web` + (VITE_API_URL gol = same-origin; nginx-ul din imagine proxează `/api`, `/agent-v3`, `/auth`) +2. **Mobil** — `android/didi-app-source/.env`: `EXPO_PUBLIC_DIDI_BASE_URL=http://` + (model în `.env.example`; fișierul `.env` nu se versionează) +3. **Extensie** — `extension/config.js` (`DIDI_HOST_HTTP` / `DIDI_HOST_HTTPS`) + +Detalii complete de instalare/instanțiere: `artefacte/README_LIVRARE.md`. + +## Build rapid + +```bash +# Web (imagine Docker) +docker build --build-arg VITE_API_URL= --build-arg VITE_KEYCLOAK_URL=https:///auth \ + -t didi-frontend:local ./web +docker compose -f docker-compose.local.yml up -d + +# Mobil (APK release; necesită JDK 17 + Android SDK — vezi docs) +cd android/didi-app-source && cp .env.example .env # completează adresa +cd android && ./gradlew assembleRelease + +# Extensie: chrome://extensions → Developer mode → Load unpacked → folderul extension/ +``` + +## Autentificare + +Toate cele 3 componente folosesc contul unic al platformei (Keycloak, realm `didi-clients`): +clienți OIDC `didi-web-app`, `didi-mobile-app`, `didi-extension` (public, PKCE S256). +Extensia are ID stabil `blcalchooinkphpnlkkhlklbmgppmnfm` (cheie în manifest) — pe el este +înregistrat redirect-ul OIDC `https://.chromiumapp.org/*`. + +--- +*Livrare Lot 3 · 17.07.2026* diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..b859d10 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,86 @@ +# DIDI -- Test Report + +Tester: _______________ +Data: _______________ + + +--- + + +## 1. Input + +### Text +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### URL +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Imagine +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Video +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Audio +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + + +--- + + +## 2. Core App + +### Comportament (timeout, crash, bug) +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Fine-tune (ce trebuie ajustat) +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + + +--- + + +## 3. Results + +### Manipulation Techniques +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Claims +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Domain +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + +### Verdict +| Ce s-a testat | Rezultat | Observatii | +|---------------|----------|------------| +| | | | + + +--- + + +## 4. Feedback +| Ce nu merge / ce trebuie schimbat | Prioritate | Detalii | +|------------------------------------|------------|---------| +| | | | diff --git a/android/didi-app-source/.env.example b/android/didi-app-source/.env.example new file mode 100644 index 0000000..cd93302 --- /dev/null +++ b/android/didi-app-source/.env.example @@ -0,0 +1,4 @@ +# Adresa platformei DiDi (nginx frontend / gateway). Exemple: +# LAN integrare: http://10.11.10.18 +# Productie: https://didi.exemplu.ro +EXPO_PUBLIC_DIDI_BASE_URL= diff --git a/android/didi-app-source/.gitignore b/android/didi-app-source/.gitignore new file mode 100644 index 0000000..4dd1509 --- /dev/null +++ b/android/didi-app-source/.gitignore @@ -0,0 +1,47 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +# Proiectul nativ /android ESTE versionat în această livrare (conține patch-uri +# manuale de manifest — share intents, cleartext — care s-ar pierde la `expo prebuild`). +/ios +.env +# fișiere locale de build din proiectul nativ +/android/local.properties +/android/app/build/ +/android/.gradle/ +/android/build/ diff --git a/android/didi-app-source/App.tsx b/android/didi-app-source/App.tsx new file mode 100644 index 0000000..8afd84e --- /dev/null +++ b/android/didi-app-source/App.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { View, ActivityIndicator } from 'react-native'; +import { StatusBar } from 'expo-status-bar'; +import { ShareIntentProvider } from 'expo-share-intent'; +import { useFonts } from 'expo-font'; +import AppNavigator from './src/navigation/AppNavigator'; +import { COLORS } from './src/theme/colors'; + +export default function App() { + const [fontsLoaded] = useFonts({ + 'Merriweather-Regular': require('./assets/fonts/Merriweather-Regular.ttf'), + 'Merriweather-Italic': require('./assets/fonts/Merriweather-Italic.ttf'), + 'Merriweather-Bold': require('./assets/fonts/Merriweather-Bold.ttf'), + }); + + if (!fontsLoaded) { + return ( + + + + ); + } + + return ( + + + + + ); +} diff --git a/android/didi-app-source/android/.gitignore b/android/didi-app-source/android/.gitignore new file mode 100644 index 0000000..8a6be07 --- /dev/null +++ b/android/didi-app-source/android/.gitignore @@ -0,0 +1,16 @@ +# OSX +# +.DS_Store + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ + +# Bundle artifacts +*.jsbundle diff --git a/android/didi-app-source/android/app/build.gradle b/android/didi-app-source/android/app/build.gradle new file mode 100644 index 0000000..35ba1bb --- /dev/null +++ b/android/didi-app-source/android/app/build.gradle @@ -0,0 +1,182 @@ +apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" +apply plugin: "com.facebook.react" + +def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() + +/** + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) + reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() + hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" + codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() + + enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean() + // Use Expo CLI to bundle the app, this ensures the Metro config + // works correctly with Expo projects. + cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) + bundleCommand = "export:embed" + + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() +} + +/** + * Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization). + */ +def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean() + +/** + * The preferred build flavor of JavaScriptCore (JSC) + * + * For example, to use the international variant, you can use: + * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' + +android { + ndkVersion rootProject.ext.ndkVersion + + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion + + namespace 'eu.didi365.mobile' + defaultConfig { + applicationId 'eu.didi365.mobile' + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0.0" + + buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // see https://reactnative.dev/docs/signed-apk-android. + signingConfig signingConfigs.debug + def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false' + shrinkResources enableShrinkResources.toBoolean() + minifyEnabled enableMinifyInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true' + crunchPngs enablePngCrunchInRelease.toBoolean() + } + } + packagingOptions { + jniLibs { + def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false' + useLegacyPackaging enableLegacyPackaging.toBoolean() + } + } + androidResources { + ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~' + } +} + +// Apply static values from `gradle.properties` to the `android.packagingOptions` +// Accepts values in comma delimited lists, example: +// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini +["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> + // Split option: 'foo,bar' -> ['foo', 'bar'] + def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); + // Trim all elements in place. + for (i in 0.. 0) { + println "android.packagingOptions.$prop += $options ($options.length)" + // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' + options.each { + android.packagingOptions[prop] += it + } + } +} + +dependencies { + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + + def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; + def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; + def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; + + if (isGifEnabled) { + // For animated gif support + implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}") + } + + if (isWebpEnabled) { + // For webp support + implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}") + if (isWebpAnimatedEnabled) { + // Animated webp support + implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}") + } + } + + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") + } else { + implementation jscFlavor + } +} diff --git a/android/didi-app-source/android/app/debug.keystore b/android/didi-app-source/android/app/debug.keystore new file mode 100644 index 0000000..364e105 Binary files /dev/null and b/android/didi-app-source/android/app/debug.keystore differ diff --git a/android/didi-app-source/android/app/proguard-rules.pro b/android/didi-app-source/android/app/proguard-rules.pro new file mode 100644 index 0000000..551eb41 --- /dev/null +++ b/android/didi-app-source/android/app/proguard-rules.pro @@ -0,0 +1,14 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# react-native-reanimated +-keep class com.swmansion.reanimated.** { *; } +-keep class com.facebook.react.turbomodule.** { *; } + +# Add any project specific keep options here: diff --git a/android/didi-app-source/android/app/src/debug/AndroidManifest.xml b/android/didi-app-source/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..3ec2507 --- /dev/null +++ b/android/didi-app-source/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/android/didi-app-source/android/app/src/debugOptimized/AndroidManifest.xml b/android/didi-app-source/android/app/src/debugOptimized/AndroidManifest.xml new file mode 100644 index 0000000..3ec2507 --- /dev/null +++ b/android/didi-app-source/android/app/src/debugOptimized/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/android/didi-app-source/android/app/src/main/AndroidManifest.xml b/android/didi-app-source/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..499bdb8 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/java/eu/didi365/mobile/MainActivity.kt b/android/didi-app-source/android/app/src/main/java/eu/didi365/mobile/MainActivity.kt new file mode 100644 index 0000000..76645a9 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/java/eu/didi365/mobile/MainActivity.kt @@ -0,0 +1,61 @@ +package eu.didi365.mobile + +import android.os.Build +import android.os.Bundle + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +import expo.modules.ReactActivityDelegateWrapper + +class MainActivity : ReactActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + // Set the theme to AppTheme BEFORE onCreate to support + // coloring the background, status bar, and navigation bar. + // This is required for expo-splash-screen. + setTheme(R.style.AppTheme); + super.onCreate(null) + } + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "main" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate { + return ReactActivityDelegateWrapper( + this, + BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, + object : DefaultReactActivityDelegate( + this, + mainComponentName, + fabricEnabled + ){}) + } + + /** + * Align the back button behavior with Android S + * where moving root activities to background instead of finishing activities. + * @see onBackPressed + */ + override fun invokeDefaultOnBackPressed() { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!moveTaskToBack(false)) { + // For non-root activities, use the default implementation to finish them. + super.invokeDefaultOnBackPressed() + } + return + } + + // Use the default back button implementation on Android S + // because it's doing more than [Activity.moveTaskToBack] in fact. + super.invokeDefaultOnBackPressed() + } +} diff --git a/android/didi-app-source/android/app/src/main/java/eu/didi365/mobile/MainApplication.kt b/android/didi-app-source/android/app/src/main/java/eu/didi365/mobile/MainApplication.kt new file mode 100644 index 0000000..e336e57 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/java/eu/didi365/mobile/MainApplication.kt @@ -0,0 +1,56 @@ +package eu.didi365.mobile + +import android.app.Application +import android.content.res.Configuration + +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.ReactHost +import com.facebook.react.common.ReleaseLevel +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint +import com.facebook.react.defaults.DefaultReactNativeHost + +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ReactNativeHostWrapper + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( + this, + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + } + ) + + override val reactHost: ReactHost + get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + DefaultNewArchitectureEntryPoint.releaseLevel = try { + ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase()) + } catch (e: IllegalArgumentException) { + ReleaseLevel.STABLE + } + loadReactNative(this) + ApplicationLifecycleDispatcher.onApplicationCreate(this) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) + } +} diff --git a/android/didi-app-source/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png b/android/didi-app-source/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png new file mode 100644 index 0000000..ed43d9a Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png differ diff --git a/android/didi-app-source/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png b/android/didi-app-source/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png new file mode 100644 index 0000000..16c60f7 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png differ diff --git a/android/didi-app-source/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png b/android/didi-app-source/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png new file mode 100644 index 0000000..1e7d8c3 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png differ diff --git a/android/didi-app-source/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png b/android/didi-app-source/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png new file mode 100644 index 0000000..f922e98 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png differ diff --git a/android/didi-app-source/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png b/android/didi-app-source/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png new file mode 100644 index 0000000..417e41b Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png differ diff --git a/android/didi-app-source/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/didi-app-source/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..883b2a0 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/didi-app-source/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 0000000..5c25e72 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/didi-app-source/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..3941bea --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/didi-app-source/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..3941bea --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..2e901a3 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..a10df67 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..f71b181 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..96a9ee2 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..54fe39d Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..6937bb3 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..cc2b4a4 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..7e889ec Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e2856d4 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..d444755 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..a623aeb Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9dc4ef4 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..f4163a7 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..1278291 Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..7867bcf Binary files /dev/null and b/android/didi-app-source/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/didi-app-source/android/app/src/main/res/values-night/colors.xml b/android/didi-app-source/android/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..3c05de5 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/values-night/colors.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/res/values/colors.xml b/android/didi-app-source/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..bc189ba --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + #060010 + #0d1424 + #023c69 + #060010 + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/res/values/strings.xml b/android/didi-app-source/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..4706b73 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + didi + contain + false + \ No newline at end of file diff --git a/android/didi-app-source/android/app/src/main/res/values/styles.xml b/android/didi-app-source/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..73bf8c3 --- /dev/null +++ b/android/didi-app-source/android/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/android/didi-app-source/android/build.gradle b/android/didi-app-source/android/build.gradle new file mode 100644 index 0000000..0554dd1 --- /dev/null +++ b/android/didi-app-source/android/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath('com.android.tools.build:gradle') + classpath('com.facebook.react:react-native-gradle-plugin') + classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://www.jitpack.io' } + } +} + +apply plugin: "expo-root-project" +apply plugin: "com.facebook.react.rootproject" diff --git a/android/didi-app-source/android/gradle.properties b/android/didi-app-source/android/gradle.properties new file mode 100644 index 0000000..85bd715 --- /dev/null +++ b/android/didi-app-source/android/gradle.properties @@ -0,0 +1,67 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx6144m -XX:MaxMetaspaceSize=1024m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Enable AAPT2 PNG crunching +android.enablePngCrunchInReleaseBuilds=true + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=true + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true + +# Use this property to enable edge-to-edge display support. +# This allows your app to draw behind system bars for an immersive UI. +# Note: Only works with ReactActivity and should not be used with custom Activity. +edgeToEdgeEnabled=true + +# Enable GIF support in React Native images (~200 B increase) +expo.gif.enabled=true +# Enable webp support in React Native images (~85 KB increase) +expo.webp.enabled=true +# Enable animated webp support (~3.4 MB increase) +# Disabled by default because iOS doesn't support animated webp +expo.webp.animated=false + +# Enable network inspector +EX_DEV_CLIENT_NETWORK_INSPECTOR=true + +# Use legacy packaging to compress native libraries in the resulting APK. +expo.useLegacyPackaging=false + +# Specifies whether the app is configured to use edge-to-edge via the app config or plugin +# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge. +expo.edgeToEdgeEnabled=true +# JDK-ul (17) se setează prin variabila de mediu JAVA_HOME sau, opțional, aici: +# org.gradle.java.home=/cale/catre/jdk-17 diff --git a/android/didi-app-source/android/gradle/wrapper/gradle-wrapper.jar b/android/didi-app-source/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/android/didi-app-source/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/didi-app-source/android/gradle/wrapper/gradle-wrapper.properties b/android/didi-app-source/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d4081da --- /dev/null +++ b/android/didi-app-source/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/didi-app-source/android/gradlew b/android/didi-app-source/android/gradlew new file mode 100755 index 0000000..7f94d3d --- /dev/null +++ b/android/didi-app-source/android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/didi-app-source/android/gradlew.bat b/android/didi-app-source/android/gradlew.bat new file mode 100644 index 0000000..5eed7ee --- /dev/null +++ b/android/didi-app-source/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/didi-app-source/android/settings.gradle b/android/didi-app-source/android/settings.gradle new file mode 100644 index 0000000..f7a8783 --- /dev/null +++ b/android/didi-app-source/android/settings.gradle @@ -0,0 +1,39 @@ +pluginManagement { + def reactNativeGradlePlugin = new File( + providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })") + }.standardOutput.asText.get().trim() + ).getParentFile().absolutePath + includeBuild(reactNativeGradlePlugin) + + def expoPluginsPath = new File( + providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })") + }.standardOutput.asText.get().trim(), + "../android/expo-gradle-plugin" + ).absolutePath + includeBuild(expoPluginsPath) +} + +plugins { + id("com.facebook.react.settings") + id("expo-autolinking-settings") +} + +extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> + if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { + ex.autolinkLibrariesFromCommand() + } else { + ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand) + } +} +expoAutolinking.useExpoModules() + +rootProject.name = 'didi' + +expoAutolinking.useExpoVersionCatalog() + +include ':app' +includeBuild(expoAutolinking.reactNativeGradlePlugin) diff --git a/android/didi-app-source/app.json b/android/didi-app-source/app.json new file mode 100644 index 0000000..04c0a61 --- /dev/null +++ b/android/didi-app-source/app.json @@ -0,0 +1,49 @@ +{ + "expo": { + "name": "didi", + "slug": "didi-app", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "dark", + "scheme": "didi", + "newArchEnabled": true, + "splash": { + "image": "./assets/splash-icon.png", + "resizeMode": "contain", + "backgroundColor": "#060010" + }, + "ios": { + "supportsTablet": true, + "bundleIdentifier": "eu.didi365.mobile" + }, + "android": { + "package": "eu.didi365.mobile", + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#0d1424" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false, + "usesCleartextTraffic": true + }, + "web": { + "favicon": "./assets/favicon.png" + }, + "plugins": [ + "expo-secure-store", + "expo-web-browser", + "expo-notifications", + [ + "expo-share-intent", + { + "androidIntentFilters": [ + "text/plain", + "image/*" + ] + } + ], + "expo-font" + ] + } +} \ No newline at end of file diff --git a/android/didi-app-source/assets/adaptive-icon.png b/android/didi-app-source/assets/adaptive-icon.png new file mode 100644 index 0000000..3b4ff08 Binary files /dev/null and b/android/didi-app-source/assets/adaptive-icon.png differ diff --git a/android/didi-app-source/assets/favicon.png b/android/didi-app-source/assets/favicon.png new file mode 100644 index 0000000..dfb23b4 Binary files /dev/null and b/android/didi-app-source/assets/favicon.png differ diff --git a/android/didi-app-source/assets/fonts/Merriweather-Bold.ttf b/android/didi-app-source/assets/fonts/Merriweather-Bold.ttf new file mode 100644 index 0000000..69db89c --- /dev/null +++ b/android/didi-app-source/assets/fonts/Merriweather-Bold.ttf @@ -0,0 +1,1482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/android/didi-app-source/assets/fonts/Merriweather-Italic.ttf b/android/didi-app-source/assets/fonts/Merriweather-Italic.ttf new file mode 100644 index 0000000..ed40329 --- /dev/null +++ b/android/didi-app-source/assets/fonts/Merriweather-Italic.ttf @@ -0,0 +1,1482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/android/didi-app-source/assets/fonts/Merriweather-Regular.ttf b/android/didi-app-source/assets/fonts/Merriweather-Regular.ttf new file mode 100644 index 0000000..7d04ad5 --- /dev/null +++ b/android/didi-app-source/assets/fonts/Merriweather-Regular.ttf @@ -0,0 +1,1482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/android/didi-app-source/assets/icon.png b/android/didi-app-source/assets/icon.png new file mode 100644 index 0000000..68a10b8 Binary files /dev/null and b/android/didi-app-source/assets/icon.png differ diff --git a/android/didi-app-source/assets/splash-icon.png b/android/didi-app-source/assets/splash-icon.png new file mode 100644 index 0000000..68a10b8 Binary files /dev/null and b/android/didi-app-source/assets/splash-icon.png differ diff --git a/android/didi-app-source/babel.config.js b/android/didi-app-source/babel.config.js new file mode 100644 index 0000000..b38c027 --- /dev/null +++ b/android/didi-app-source/babel.config.js @@ -0,0 +1,9 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + // react-native-worklets/plugin is required by react-native-reanimated v4 and + // MUST be listed last. + plugins: ['react-native-worklets/plugin'], + }; +}; diff --git a/android/didi-app-source/index.ts b/android/didi-app-source/index.ts new file mode 100644 index 0000000..1d6e981 --- /dev/null +++ b/android/didi-app-source/index.ts @@ -0,0 +1,8 @@ +import { registerRootComponent } from 'expo'; + +import App from './App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); diff --git a/android/didi-app-source/package-lock.json b/android/didi-app-source/package-lock.json new file mode 100644 index 0000000..2ca574b --- /dev/null +++ b/android/didi-app-source/package-lock.json @@ -0,0 +1,12173 @@ +{ + "name": "didi-app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "didi-app", + "version": "1.0.0", + "dependencies": { + "@expo/metro-runtime": "~6.1.2", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/native": "^7.1.28", + "@react-navigation/native-stack": "^7.13.0", + "axios": "^1.13.5", + "buffer": "^6.0.3", + "expo": "~54.0.33", + "expo-auth-session": "~7.0.10", + "expo-av": "~16.0.8", + "expo-crypto": "~15.0.8", + "expo-document-picker": "~14.0.8", + "expo-font": "~14.0.11", + "expo-image-picker": "~17.0.10", + "expo-linear-gradient": "~15.0.8", + "expo-linking": "~8.0.11", + "expo-notifications": "~0.32.16", + "expo-secure-store": "~15.0.8", + "expo-share-intent": "^5.1.1", + "expo-status-bar": "~3.0.9", + "expo-web-browser": "~15.0.10", + "lucide-react-native": "^1.14.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "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", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "babel-preset-expo": "^55.0.21", + "sharp": "^0.34.5", + "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.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/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/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/core/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/@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-compilation-targets/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/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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/@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-create-regexp-features-plugin/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/@babel/helper-define-polyfill-provider": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz", + "integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz", + "integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", + "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/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/@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", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/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/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/traverse--for-generate-function-map/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/traverse/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/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/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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/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/@expo/config-plugins/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/@expo/config-plugins/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/@expo/config-plugins/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/@expo/config-plugins/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/@expo/config-plugins/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/@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/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/devtools/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/@expo/devtools/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/@expo/devtools/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/@expo/devtools/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/@expo/devtools/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/@expo/devtools/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/@expo/env": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.0.8.tgz", + "integrity": "sha512-5VQD6GT8HIMRaSaB5JFtOXuvfDVU80YtZIuUT/GDhUF782usIXY13Tn3IdDz1Tm/lqA9qnRZQ1BF4t7LlvdJPA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "debug": "^4.3.4", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", + "getenv": "^2.0.0" + } + }, + "node_modules/@expo/env/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/@expo/env/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/@expo/env/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/@expo/env/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/@expo/env/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/@expo/env/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/@expo/fingerprint": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.15.4.tgz", + "integrity": "sha512-eYlxcrGdR2/j2M6pEDXo9zU9KXXF1vhP+V+Tl+lyY+bU8lnzrN6c637mz6Ye3em2ANy8hhUR03Raf8VsT9Ogng==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "arg": "^5.0.2", + "chalk": "^4.1.2", + "debug": "^4.3.4", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "ignore": "^5.3.1", + "minimatch": "^9.0.0", + "p-limit": "^3.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.6.0" + }, + "bin": { + "fingerprint": "bin/cli.js" + } + }, + "node_modules/@expo/fingerprint/node_modules/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/@expo/fingerprint/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/@expo/fingerprint/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/@expo/fingerprint/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/@expo/fingerprint/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/@expo/fingerprint/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/@expo/image-utils": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.8.tgz", + "integrity": "sha512-HHHaG4J4nKjTtVa1GG9PCh763xlETScfEyNxxOvfTRr8IKPJckjTyqSLEtdJoFNJ1vqiABEjW7tqGhqGibZLeA==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "chalk": "^4.0.0", + "getenv": "^2.0.0", + "jimp-compact": "0.16.1", + "parse-png": "^2.1.0", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0", + "semver": "^7.6.0", + "temp-dir": "~2.0.0", + "unique-string": "~2.0.0" + } + }, + "node_modules/@expo/image-utils/node_modules/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/@expo/image-utils/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/@expo/image-utils/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/@expo/image-utils/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/@expo/image-utils/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/@expo/image-utils/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/@expo/json-file": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.8.tgz", + "integrity": "sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.3" + } + }, + "node_modules/@expo/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-runtime": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-6.1.2.tgz", + "integrity": "sha512-nvM+Qv45QH7pmYvP8JB1G8JpScrWND3KrMA6ZKe62cwwNiX/BjHU28Ear0v/4bQWXlOY0mv6B8CDIm8JxXde9g==", + "license": "MIT", + "dependencies": { + "anser": "^1.4.9", + "pretty-format": "^29.7.0", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-dom": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@expo/osascript": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.3.8.tgz", + "integrity": "sha512-/TuOZvSG7Nn0I8c+FcEaoHeBO07yu6vwDgk7rZVvAXoeAK5rkA09jRyjYsZo+0tMEFaToBeywA6pj50Mb3ny9w==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "exec-async": "^2.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/package-manager": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.9.10.tgz", + "integrity": "sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA==", + "license": "MIT", + "dependencies": { + "@expo/json-file": "^10.0.8", + "@expo/spawn-async": "^1.7.2", + "chalk": "^4.0.0", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "resolve-workspace-root": "^2.0.0" + } + }, + "node_modules/@expo/package-manager/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/@expo/package-manager/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/@expo/package-manager/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/@expo/package-manager/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/@expo/package-manager/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/@expo/package-manager/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/@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/schema-utils": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-0.1.8.tgz", + "integrity": "sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A==", + "license": "MIT" + }, + "node_modules/@expo/sdk-runtime-versions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", + "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==", + "license": "MIT" + }, + "node_modules/@expo/spawn-async": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz", + "integrity": "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/sudo-prompt": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", + "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", + "license": "MIT" + }, + "node_modules/@expo/ws-tunnel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz", + "integrity": "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==", + "license": "MIT" + }, + "node_modules/@expo/xcpretty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.0.tgz", + "integrity": "sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "chalk": "^4.1.0", + "js-yaml": "^4.1.0" + }, + "bin": { + "excpretty": "build/cli.js" + } + }, + "node_modules/@expo/xcpretty/node_modules/@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/@expo/xcpretty/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/@expo/xcpretty/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@expo/xcpretty/node_modules/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/@expo/xcpretty/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/@expo/xcpretty/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/@expo/xcpretty/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/@expo/xcpretty/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@expo/xcpretty/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/@ide/backoff": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz", + "integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==", + "license": "MIT" + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@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/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "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/assets-registry": { + "version": "0.81.5", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", + "integrity": "sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==", + "license": "MIT", + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.81.5", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.81.5.tgz", + "integrity": "sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.81.5" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.81.5", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.81.5.tgz", + "integrity": "sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.81.5", + "babel-plugin-syntax-hermes-parser": "0.29.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.81.5", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.81.5.tgz", + "integrity": "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.29.1", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/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/@react-native/codegen/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@react-native/codegen/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@react-native/codegen/node_modules/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/@react-native/codegen/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/@react-native/codegen/node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.81.5", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.81.5.tgz", + "integrity": "sha512-yWRlmEOtcyvSZ4+OvqPabt+NS36vg0K/WADTQLhrYrm9qdZSuXmq8PmdJWz/68wAqKQ+4KTILiq2kjRQwnyhQw==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.81.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.83.1", + "metro-config": "^0.83.1", + "metro-core": "^0.83.1", + "semver": "^7.1.3" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/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/metro-babel-transformer": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.85.3.tgz", + "integrity": "sha512-omuKq+r7jM4XvCMIlNMPP7Up3SyB8o5EAdZtF7YXniKyq7UOMBqhYHFqgsdOXr0lT+3ADf7VCJG3sb82jlBrrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.85.3", + "hermes-parser": "0.33.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.85.3.tgz", + "integrity": "sha512-Wc94zGfeFG8Njf9SHMPfYZP04kjigkOps6F1TYTvd7ZVXuGxqseCDgxc50LWcOhOCLypI9n3oVVqz81C3p44ZA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.85.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-preset": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.85.3.tgz", + "integrity": "sha512-fD7fxEhkJB/aF57tWoXjaAWpklfrExYZS3k6aXPP3BQ77DZY7gvf/b7dbirwjID6NVnP1JDRJyTuPBGr0K/vlw==", + "license": "MIT", + "peer": true, + "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-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-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^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-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@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-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.85.3", + "babel-plugin-syntax-hermes-parser": "0.33.3", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/codegen": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.3.tgz", + "integrity": "sha512-/JkS1lGLyzBWP1FbgDwaqEf7qShIC6pUC1M0a/YMAd/v4iqR24MRkQWe7jkYvcBQ2LpEhs5NGE9InhxSv21zCA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.33.3", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", + "integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-parser": "0.33.3" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.33.3" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.85.3.tgz", + "integrity": "sha512-sVo6HepUmCcpdfozEf91lA0FjpLNNZYu/Zi9FiYiAQTK8pzATXDVTqhvdxpFrQn435p5eUTSbllvbH/KN+bnyA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/js-polyfills": "0.85.3", + "@react-native/metro-babel-transformer": "0.85.3", + "metro-config": "^0.84.3", + "metro-runtime": "^0.84.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/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", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/@react-native/js-polyfills": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.3.tgz", + "integrity": "sha512-U2+aMshIXf1uFn77tpBb/xhHWB9vkVrMpt7kkucAugF8hJKYTDGB587X7WwelHduK2KBfhl4giSv0rzZGoef9A==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/metro-config/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/metro-config/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.4.tgz", + "integrity": "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.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.35.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.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-config": "0.84.4", + "metro-core": "0.84.4", + "metro-file-map": "0.84.4", + "metro-resolver": "0.84.4", + "metro-runtime": "0.84.4", + "metro-source-map": "0.84.4", + "metro-symbolicate": "0.84.4", + "metro-transform-plugins": "0.84.4", + "metro-transform-worker": "0.84.4", + "mime-types": "^3.0.1", + "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 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-babel-transformer": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.4.tgz", + "integrity": "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-cache": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.4.tgz", + "integrity": "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==", + "license": "MIT", + "peer": true, + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.4" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-cache-key": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.4.tgz", + "integrity": "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-config": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.4.tgz", + "integrity": "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.84.4", + "metro-cache": "0.84.4", + "metro-core": "0.84.4", + "metro-runtime": "0.84.4", + "yaml": "^2.6.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-core": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.4.tgz", + "integrity": "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.84.4" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-file-map": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.4.tgz", + "integrity": "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==", + "license": "MIT", + "peer": true, + "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 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-minify-terser": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.4.tgz", + "integrity": "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-resolver": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.4.tgz", + "integrity": "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-runtime": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.4.tgz", + "integrity": "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-source-map": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.4.tgz", + "integrity": "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.4", + "nullthrows": "^1.1.1", + "ob1": "0.84.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-symbolicate": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.4.tgz", + "integrity": "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-transform-plugins": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.4.tgz", + "integrity": "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-transform-worker": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.4.tgz", + "integrity": "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.84.4", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-minify-terser": "0.84.4", + "metro-source-map": "0.84.4", + "metro-transform-plugins": "0.84.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/metro-config/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@react-native/metro-config/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/metro-config/node_modules/ob1": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz", + "integrity": "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.81.5", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.5.tgz", + "integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==", + "license": "MIT" + }, + "node_modules/@react-navigation/core": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.14.0.tgz", + "integrity": "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.5.3", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "query-string": "^7.1.3", + "react-is": "^19.1.0", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "react": ">= 18.2.0" + } + }, + "node_modules/@react-navigation/core/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/@react-navigation/core/node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" + }, + "node_modules/@react-navigation/elements": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.5.tgz", + "integrity": "sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3", + "use-latest-callback": "^0.2.4", + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.1.28", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } + } + }, + "node_modules/@react-navigation/native": { + "version": "7.1.28", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.28.tgz", + "integrity": "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.14.0", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" + } + }, + "node_modules/@react-navigation/native-stack": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.13.0.tgz", + "integrity": "sha512-5OOp1IKEd5woHl9hGBU0qCAfrQ4+7Tqej0HzDzGQeXzS8tg9gq84x1qUdRvFk5BXbhuAyvJliY9F1/I07d2X0A==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.5", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.1.28", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/native/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/@react-navigation/routers": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz", + "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", + "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@urql/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz", + "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==", + "license": "MIT", + "dependencies": { + "@0no-co/graphql.web": "^1.0.13", + "wonka": "^6.3.2" + } + }, + "node_modules/@urql/exchange-retry": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz", + "integrity": "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==", + "license": "MIT", + "dependencies": { + "@urql/core": "^5.1.2", + "wonka": "^6.3.2" + }, + "peerDependencies": { + "@urql/core": "^5.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.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/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "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==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "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-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/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/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/babel-plugin-react-native-web": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", + "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.29.1.tgz", + "integrity": "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.29.1" + } + }, + "node_modules/babel-plugin-syntax-hermes-parser/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/babel-plugin-syntax-hermes-parser/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/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.21", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.21.tgz", + "integrity": "sha512-anXoUZBcxydLdVs2L+r3bWKGUvZv2FtgOl8xRJ12i/YfKICBpwTGZWSTiEYTqBByZ6GkA3mE9+3TW97X2ocFTQ==", + "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.17", + "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/@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/babel-preset-expo/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/babel-preset-expo/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/babel-preset-expo/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/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/babel-plugin-syntax-hermes-parser/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-expo/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-preset-expo/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/babel-preset-expo/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/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/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/badgin": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz", + "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "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": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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.2.1" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "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==", + "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==", + "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/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.30001772", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", + "integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", + "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": "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/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/chrome-launcher/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/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/chromium-edge-launcher/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/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/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": "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/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/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/color/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/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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, + "node_modules/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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-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==", + "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==", + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/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/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==", + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "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.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "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-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-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==", + "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-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==", + "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==", + "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/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": "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/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/exec-async": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", + "integrity": "sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==", + "license": "MIT" + }, + "node_modules/expo": { + "version": "54.0.33", + "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", + "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@expo/cli": "54.0.23", + "@expo/config": "~12.0.13", + "@expo/config-plugins": "~54.0.4", + "@expo/devtools": "0.1.8", + "@expo/fingerprint": "0.15.4", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "54.0.14", + "@expo/vector-icons": "^15.0.3", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~54.0.10", + "expo-asset": "~12.0.12", + "expo-constants": "~18.0.13", + "expo-file-system": "~19.0.21", + "expo-font": "~14.0.11", + "expo-keep-awake": "~15.0.8", + "expo-modules-autolinking": "3.0.24", + "expo-modules-core": "3.0.29", + "pretty-format": "^29.7.0", + "react-refresh": "^0.14.2", + "whatwg-url-without-unicode": "8.0.0-3" + }, + "bin": { + "expo": "bin/cli", + "expo-modules-autolinking": "bin/autolinking", + "fingerprint": "bin/fingerprint" + }, + "peerDependencies": { + "@expo/dom-webview": "*", + "@expo/metro-runtime": "*", + "react": "*", + "react-native": "*", + "react-native-webview": "*" + }, + "peerDependenciesMeta": { + "@expo/dom-webview": { + "optional": true + }, + "@expo/metro-runtime": { + "optional": true + }, + "react-native-webview": { + "optional": true + } + } + }, + "node_modules/expo-application": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-7.0.8.tgz", + "integrity": "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-auth-session": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-7.0.10.tgz", + "integrity": "sha512-XDnKkudvhHSKkZfJ+KkodM+anQcrxB71i+h0kKabdLa5YDXTQ81aC38KRc3TMqmnBDHAu0NpfbzEVd9WDFY3Qg==", + "license": "MIT", + "dependencies": { + "expo-application": "~7.0.8", + "expo-constants": "~18.0.11", + "expo-crypto": "~15.0.8", + "expo-linking": "~8.0.10", + "expo-web-browser": "~15.0.10", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-av": { + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/expo-av/-/expo-av-16.0.8.tgz", + "integrity": "sha512-cmVPftGR/ca7XBgs7R6ky36lF3OC0/MM/lpgX/yXqfv0jASTsh7AYX9JxHCwFmF+Z6JEB1vne9FDx4GiLcGreQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "node_modules/expo-constants": { + "version": "18.0.13", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", + "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==", + "license": "MIT", + "dependencies": { + "@expo/config": "~12.0.13", + "@expo/env": "~2.0.8" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-crypto": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.8.tgz", + "integrity": "sha512-aF7A914TB66WIlTJvl5J6/itejfY78O7dq3ibvFltL9vnTALJ/7LYHvLT4fwmx9yUNS6ekLBtDGWivFWnj2Fcw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-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-font": { + "version": "14.0.11", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", + "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", + "license": "MIT", + "dependencies": { + "fontfaceobserver": "^2.1.0" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-image-loader": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz", + "integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-picker": { + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-17.0.10.tgz", + "integrity": "sha512-a2xrowp2trmvXyUWgX3O6Q2rZaa2C59AqivKI7+bm+wLvMfTEbZgldLX4rEJJhM8xtmEDTNU+lzjtObwzBRGaw==", + "license": "MIT", + "dependencies": { + "expo-image-loader": "~6.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-linear-gradient": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-15.0.8.tgz", + "integrity": "sha512-V2d8Wjn0VzhPHO+rrSBtcl+Fo+jUUccdlmQ6OoL9/XQB7Qk3d9lYrqKDJyccwDxmQT10JdST3Tmf2K52NLc3kw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-linking": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz", + "integrity": "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA==", + "license": "MIT", + "dependencies": { + "expo-constants": "~18.0.12", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-modules-autolinking": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz", + "integrity": "sha512-TP+6HTwhL7orDvsz2VzauyQlXJcAWyU3ANsZ7JGL4DQu8XaZv/A41ZchbtAYLfozNA2Ya1Hzmhx65hXryBMjaQ==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "require-from-string": "^2.0.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "expo-modules-autolinking": "bin/expo-modules-autolinking.js" + } + }, + "node_modules/expo-modules-autolinking/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/expo-modules-autolinking/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/expo-modules-autolinking/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/expo-modules-autolinking/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/expo-modules-autolinking/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/expo-modules-autolinking/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/expo-modules-core": { + "version": "3.0.29", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-3.0.29.tgz", + "integrity": "sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-notifications": { + "version": "0.32.16", + "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.32.16.tgz", + "integrity": "sha512-QQD/UA6v7LgvwIJ+tS7tSvqJZkdp0nCSj9MxsDk/jU1GttYdK49/5L2LvE/4U0H7sNBz1NZAyhDZozg8xgBLXw==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.8.8", + "@ide/backoff": "^1.0.0", + "abort-controller": "^3.0.0", + "assert": "^2.0.0", + "badgin": "^1.1.5", + "expo-application": "~7.0.8", + "expo-constants": "~18.0.13" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-secure-store": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-15.0.8.tgz", + "integrity": "sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-server": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.5.tgz", + "integrity": "sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA==", + "license": "MIT", + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/expo-share-intent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/expo-share-intent/-/expo-share-intent-5.1.1.tgz", + "integrity": "sha512-0sEf34+4w/ySQd7xZmnog/oOm1q+PUBHFoGU97mxTXIGijY4LNmSX806efrDkYMwCxgRA1iHxG/zBib4zBPmYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/achorein" + }, + "https://www.buymeacoffee.com/achorein" + ], + "license": "MIT", + "dependencies": { + "@expo/config-plugins": "~10.1.1", + "expo-constants": "~18.0.10", + "expo-linking": "~8.0.9" + }, + "peerDependencies": { + "expo": "^54", + "expo-constants": ">=18.0.8", + "expo-linking": ">=8.0.8", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-share-intent/node_modules/@expo/config-plugins": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.2.tgz", + "integrity": "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==", + "license": "MIT", + "dependencies": { + "@expo/config-types": "^53.0.5", + "@expo/json-file": "~9.1.5", + "@expo/plist": "^0.3.5", + "@expo/sdk-runtime-versions": "^1.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.5", + "getenv": "^2.0.0", + "glob": "^10.4.2", + "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-share-intent/node_modules/@expo/config-types": { + "version": "53.0.5", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.5.tgz", + "integrity": "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==", + "license": "MIT" + }, + "node_modules/expo-share-intent/node_modules/@expo/json-file": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz", + "integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.3" + } + }, + "node_modules/expo-share-intent/node_modules/@expo/plist": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.3.5.tgz", + "integrity": "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.2.3", + "xmlbuilder": "^15.1.1" + } + }, + "node_modules/expo-share-intent/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/expo-share-intent/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/expo-share-intent/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/expo-share-intent/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/expo-share-intent/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expo-share-intent/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/expo-share-intent/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/expo-share-intent/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expo-share-intent/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/expo-status-bar": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-3.0.9.tgz", + "integrity": "sha512-xyYyVg6V1/SSOZWh4Ni3U129XHCnFHBTcUo0dhWtFDrZbNp/duw5AGsQfb2sVeU0gxWHXSY1+5F0jnKYC7WuOw==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "^1.2.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-web-browser": { + "version": "15.0.10", + "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.10.tgz", + "integrity": "sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/@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/expo/node_modules/@expo/cli": { + "version": "54.0.23", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.23.tgz", + "integrity": "sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g==", + "license": "MIT", + "dependencies": { + "@0no-co/graphql.web": "^1.0.8", + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~12.0.13", + "@expo/config-plugins": "~54.0.4", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.0.8", + "@expo/image-utils": "^0.8.8", + "@expo/json-file": "^10.0.8", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "~54.0.14", + "@expo/osascript": "^2.3.8", + "@expo/package-manager": "^1.9.10", + "@expo/plist": "^0.4.8", + "@expo/prebuild-config": "^54.0.8", + "@expo/schema-utils": "^0.1.8", + "@expo/spawn-async": "^1.7.2", + "@expo/ws-tunnel": "^1.0.1", + "@expo/xcpretty": "^4.3.0", + "@react-native/dev-middleware": "0.81.5", + "@urql/core": "^5.0.6", + "@urql/exchange-retry": "^1.3.0", + "accepts": "^1.3.8", + "arg": "^5.0.2", + "better-opn": "~3.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "env-editor": "^0.4.1", + "expo-server": "^1.0.5", + "freeport-async": "^2.0.0", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.1.6", + "minimatch": "^9.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^3.0.1", + "pretty-bytes": "^5.6.0", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "qrcode-terminal": "0.11.0", + "require-from-string": "^2.0.2", + "requireg": "^0.2.2", + "resolve": "^1.22.2", + "resolve-from": "^5.0.0", + "resolve.exports": "^2.0.3", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "source-map-support": "~0.5.21", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "tar": "^7.5.2", + "terminal-link": "^2.1.1", + "undici": "^6.18.2", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1" + }, + "bin": { + "expo-internal": "build/bin/cli" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/cli/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/node_modules/@expo/metro-config": { + "version": "54.0.14", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-54.0.14.tgz", + "integrity": "sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.5", + "@expo/config": "~12.0.13", + "@expo/env": "~2.0.8", + "@expo/json-file": "~10.0.8", + "@expo/metro": "~54.2.0", + "@expo/spawn-async": "^1.7.2", + "browserslist": "^4.25.0", + "chalk": "^4.1.0", + "debug": "^4.3.2", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "hermes-parser": "^0.29.1", + "jsc-safe-url": "^0.2.4", + "lightningcss": "^1.30.1", + "minimatch": "^9.0.0", + "postcss": "~8.4.32", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "expo": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/vector-icons": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz", + "integrity": "sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==", + "license": "MIT", + "peerDependencies": { + "expo-font": ">=14.0.4", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo/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/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/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/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/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/expo/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/expo/node_modules/expo-asset": { + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.12.tgz", + "integrity": "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.8.8", + "expo-constants": "~18.0.12" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/expo-file-system": { + "version": "19.0.21", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz", + "integrity": "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo/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/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/expo/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/expo/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/expo/node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/expo/node_modules/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/expo/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "license": "MIT" + }, + "node_modules/fbjs/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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.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/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==", + "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==", + "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-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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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-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/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==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "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==", + "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==", + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.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/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/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "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-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-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==", + "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-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "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-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==", + "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-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==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "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/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/istanbul-lib-instrument/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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "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-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-message-util/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/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/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/jest-util/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/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/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/jest-util/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/jest-util/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/jest-util/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/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-validate/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/jest-validate/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/jest-validate/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/jest-validate/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/jest-validate/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/jest-validate/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/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/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/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-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lan-network": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.1.7.tgz", + "integrity": "sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==", + "license": "MIT", + "bin": { + "lan-network": "dist/lan-network-cli.js" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "license": "MIT", + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react-native": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-1.14.0.tgz", + "integrity": "sha512-FkOjd4JHIB8SplakHQjeo4RR/5peXtl+PbL+TghQqWzcQ+AKbJ/PFl5xEnerLtSQ4EIq6B9uXblY7QSyKg05WQ==", + "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==", + "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-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/@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/metro/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/metro/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/metro/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/metro/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/metro/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/metro/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/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.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.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nested-error-stacks": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz", + "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/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-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "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==", + "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==", + "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/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/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/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-png": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", + "integrity": "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==", + "license": "MIT", + "dependencies": { + "pngjs": "^3.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.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.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/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==", + "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/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/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/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "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/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-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": "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/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-is-edge-to-edge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", + "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-reanimated": { + "version": "4.1.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-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-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-webview/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/react-native-worklets": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.3.tgz", + "integrity": "sha512-oCBJROyLU7yG/1R8s0INMflygTH71bx+5XcYkH0CM938TlhSoVbiunE1WVW5FZa51vwYqfLie/IXMX2s1Kh3eg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" + } + }, + "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/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/react-native/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/react-native/node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requireg": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz", + "integrity": "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==", + "dependencies": { + "nested-error-stacks": "~2.0.1", + "rc": "~1.2.7", + "resolve": "~1.7.1" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/requireg/node_modules/resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "license": "MIT", + "dependencies": { + "path-parse": "^1.0.5" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "license": "MIT", + "dependencies": { + "global-dirs": "^0.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-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/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/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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-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==", + "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.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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/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==", + "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/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/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-plist": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", + "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", + "license": "MIT", + "dependencies": { + "bplist-creator": "0.1.0", + "bplist-parser": "0.3.1", + "plist": "^3.0.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", + "license": "Unlicense", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string-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-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/structured-headers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", + "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", + "license": "MIT" + }, + "node_modules/styleq": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", + "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "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/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-hyperlinks/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/supports-hyperlinks/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-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", + "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "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/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/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/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-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-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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": "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/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/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/whatwg-url/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/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-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "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.5", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", + "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xcode": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", + "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", + "license": "Apache-2.0", + "dependencies": { + "simple-plist": "^1.1.0", + "uuid": "^7.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/xml2js": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", + "integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/android/didi-app-source/package.json b/android/didi-app-source/package.json new file mode 100644 index 0000000..95198ec --- /dev/null +++ b/android/didi-app-source/package.json @@ -0,0 +1,51 @@ +{ + "name": "didi-app", + "version": "1.0.0", + "main": "index.ts", + "scripts": { + "start": "expo start", + "android": "expo run:android", + "ios": "expo run:ios", + "web": "expo start --web" + }, + "dependencies": { + "@expo/metro-runtime": "~6.1.2", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/native": "^7.1.28", + "@react-navigation/native-stack": "^7.13.0", + "axios": "^1.13.5", + "buffer": "^6.0.3", + "expo": "~54.0.33", + "expo-auth-session": "~7.0.10", + "expo-av": "~16.0.8", + "expo-crypto": "~15.0.8", + "expo-document-picker": "~14.0.8", + "expo-font": "~14.0.11", + "expo-image-picker": "~17.0.10", + "expo-linear-gradient": "~15.0.8", + "expo-linking": "~8.0.11", + "expo-notifications": "~0.32.16", + "expo-secure-store": "~15.0.8", + "expo-share-intent": "^5.1.1", + "expo-status-bar": "~3.0.9", + "expo-web-browser": "~15.0.10", + "lucide-react-native": "^1.14.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "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", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@types/react": "~19.1.0", + "babel-preset-expo": "^55.0.21", + "sharp": "^0.34.5", + "typescript": "~5.9.2" + }, + "private": true +} diff --git a/android/didi-app-source/scripts/generate-icons.js b/android/didi-app-source/scripts/generate-icons.js new file mode 100644 index 0000000..3cacd02 --- /dev/null +++ b/android/didi-app-source/scripts/generate-icons.js @@ -0,0 +1,80 @@ +const sharp = require('sharp'); +const path = require('path'); + +const ASSETS = path.join(__dirname, '..', 'assets'); + +function gradient(id) { + return ` + + + `; +} + +// Full square icon (iOS + fallback) +function makeIcon(size) { + const ts = Math.round(size * 0.32); + const cx = size / 2; + const cy = size / 2 + ts * 0.1; + return ` + ${gradient('bg')} + + didi +`; +} + +// Adaptive foreground: FULL SQUARE gradient + text (no circle!) +// Android handles the masking (circle, squircle, etc.) +function makeAdaptive(size) { + const ts = Math.round(size * 0.25); + const cx = size / 2; + const cy = size / 2 + ts * 0.1; + return ` + ${gradient('fg')} + + didi +`; +} + +// Favicon +function makeFavicon(size) { + const ts = Math.round(size * 0.40); + return ` + ${gradient('fv')} + + didi +`; +} + +async function main() { + await sharp(Buffer.from(makeIcon(1024))) + .png().toFile(path.join(ASSETS, 'icon.png')); + console.log('icon.png'); + + await sharp(Buffer.from(makeAdaptive(1024))) + .png().toFile(path.join(ASSETS, 'adaptive-icon.png')); + console.log('adaptive-icon.png'); + + await sharp(Buffer.from(makeIcon(1024))) + .png().toFile(path.join(ASSETS, 'splash-icon.png')); + console.log('splash-icon.png'); + + await sharp(Buffer.from(makeFavicon(256))) + .resize(48, 48).png().toFile(path.join(ASSETS, 'favicon.png')); + console.log('favicon.png'); + + console.log('Done!'); +} + +main().catch(console.error); diff --git a/android/didi-app-source/sdsd.jpeg b/android/didi-app-source/sdsd.jpeg new file mode 100644 index 0000000..7a09c87 Binary files /dev/null and b/android/didi-app-source/sdsd.jpeg differ diff --git a/android/didi-app-source/src/api/analysis.ts b/android/didi-app-source/src/api/analysis.ts new file mode 100644 index 0000000..a4f1fcd --- /dev/null +++ b/android/didi-app-source/src/api/analysis.ts @@ -0,0 +1,136 @@ +import { skillsApi } from './axios'; +import { TIMEOUTS } from './config'; +import type { + UploadResponse, + AnalysisSession, + V3Response, + V3QueueStatus, + V3AsyncSubmitData, +} from '../types/analysis'; + +export async function uploadMedia( + fileUri: string, + fileName: string, + fileType: string, + userId: string +): Promise { + const formData = new FormData(); + formData.append('file', { + uri: fileUri, + name: fileName, + type: fileType, + } as any); + formData.append('user_id', userId); + + const response = await skillsApi.post>('media/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + timeout: TIMEOUTS.UPLOAD, + }); + + const data = response.data?.data; + return data?.public_url || ''; +} + +const ALLOWED_SKILLS = ['techniques', 'claims', 'ai-tampered', 'domain', 'context', 'source-assessment']; + +export async function analyzeSkill( + skill: string, + body: { text?: string; media_url?: string; user_id?: string; [key: string]: any } +): Promise { + if (!ALLOWED_SKILLS.includes(skill)) { + throw new Error(`Invalid skill: ${skill}`); + } + const endpoint = body.media_url + ? `${skill}/analyze-media` + : `${skill}/analyze`; + const response = await skillsApi.post( + endpoint, + body, + { timeout: TIMEOUTS.SKILL } + ); + return response.data?.data || response.data as any; +} + +export async function analyzePipeline( + body: { text?: string; media_url?: string; media_type?: string; user_id?: string; plan_type?: number; [key: string]: any } +): Promise { + const endpoint = body.media_url + ? 'pipeline/analyze-media' + : 'pipeline/analyze'; + const response = await skillsApi.post( + endpoint, + body, + { timeout: TIMEOUTS.SKILL } + ); + return response.data; +} + +export async function submitPipelineAsync( + body: Record +): Promise<{ session_id: string; async: boolean; data: V3AsyncSubmitData | AnalysisSession }> { + const response = await skillsApi.post>( + 'pipeline/analyze-async', + body, + { timeout: TIMEOUTS.SKILL } + ); + + const res = response.data; + + // Async path: 202 — session_id is inside data + if (res.async && res.data?.session_id) { + return { session_id: res.data.session_id, async: true, data: res.data }; + } + + // Sync fallback: RabbitMQ down — full result returned directly + return { session_id: (res.data as any)?.session_id || '', async: false, data: res.data }; +} + +export async function submitSkillAsync( + skill: string, + body: Record +): Promise<{ session_id: string; async: boolean; data: V3AsyncSubmitData | AnalysisSession }> { + if (!ALLOWED_SKILLS.includes(skill)) { + throw new Error(`Invalid skill: ${skill}`); + } + const response = await skillsApi.post>( + `${skill}/analyze-async`, + body, + { timeout: TIMEOUTS.SKILL } + ); + + const res = response.data; + + // Async path: session_id is inside data + if (res.async && res.data?.session_id) { + return { session_id: res.data.session_id, async: true, data: res.data }; + } + + // Sync fallback: full result returned directly + return { session_id: (res.data as any)?.session_id || '', async: false, data: res.data }; +} + +export async function pollQueueStatus(sessionId: string, signal?: AbortSignal): Promise { + const response = await skillsApi.get>( + `pipeline/${encodeURIComponent(sessionId)}/queue-status`, + { signal } + ); + + const session = response.data?.data; + const queue = session?._queue; + + return { + session, + progress: queue?.progress || 0, + total_components: queue?.total_components || 5, + completed_components: queue?.completed_components || [], + status: session?.status || 'pending', + }; +} + +export async function getPipelineResult(sessionId: string, signal?: AbortSignal): Promise { + const response = await skillsApi.get( + `pipeline/${encodeURIComponent(sessionId)}/result`, + { signal } + ); + return response.data?.data || response.data as any; +} diff --git a/android/didi-app-source/src/api/auth.ts b/android/didi-app-source/src/api/auth.ts new file mode 100644 index 0000000..1044088 --- /dev/null +++ b/android/didi-app-source/src/api/auth.ts @@ -0,0 +1,41 @@ +import axios from 'axios'; +import { AUTH_CONFIG, getRedirectUri } from './config'; +import { mainApi } from './axios'; +import { TokenResponse, UserProfile } from '../types/auth'; + +export async function exchangeCodeForTokens( + code: string, + codeVerifier: string +): Promise { + const params = new URLSearchParams({ + grant_type: 'authorization_code', + client_id: AUTH_CONFIG.CLIENT_ID, + code, + redirect_uri: getRedirectUri(), + code_verifier: codeVerifier, + }); + + const response = await axios.post(AUTH_CONFIG.TOKEN_URL, params.toString(), { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + timeout: 30000, + }); + + return response.data; +} + +export async function fetchProfile(): Promise { + const response = await mainApi.get('auth/me'); + // API returns { success: true, data: { ... } } + return response.data?.data || response.data; +} + +export async function fetchSubscriptionUsage() { + const response = await mainApi.get('subscriptions/usage'); + // API returns { success: true, data: { credits, plan, subscription } } + return response.data?.data || response.data; +} + +export async function fetchCredits(): Promise<{ credits_remained: number; credits_spent: number }> { + const response = await mainApi.get('auth/credits'); + return response.data?.data || response.data; +} diff --git a/android/didi-app-source/src/api/axios.ts b/android/didi-app-source/src/api/axios.ts new file mode 100644 index 0000000..01e76f9 --- /dev/null +++ b/android/didi-app-source/src/api/axios.ts @@ -0,0 +1,62 @@ +import axios from 'axios'; +import { Platform } from 'react-native'; +import { API_CONFIG } from './config'; +import { useAuthStore } from '../store/authStore'; + +const USER_AGENT = + 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 DidiApp/1.0'; + +// Browser forbids setting User-Agent — only set on native +const nativeHeaders = Platform.OS !== 'web' ? { 'User-Agent': USER_AGENT } : {}; + +if (Platform.OS !== 'web') { + axios.defaults.headers.common['User-Agent'] = USER_AGENT; +} + +export const skillsApi = axios.create({ + baseURL: API_CONFIG.SKILLS_API, + timeout: 300000, + headers: nativeHeaders, +}); + +export const mainApi = axios.create({ + baseURL: API_CONFIG.MAIN_API, + timeout: 30000, + headers: nativeHeaders, +}); + +// Request interceptor - attach token & auto-refresh +const attachToken = async (config: any) => { + const { accessToken, expiresAt, refreshAccessToken } = useAuthStore.getState(); + + if (expiresAt && Date.now() > expiresAt - 30000) { + const newToken = await refreshAccessToken(); + if (newToken) { + config.headers.Authorization = `Bearer ${newToken}`; + } + } else if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + + return config; +}; + +// Response interceptor - retry on 401 (max once to prevent infinite loops) +const handle401 = async (error: any) => { + const config = error.config; + if (error.response?.status === 401 && !config?._retried) { + config._retried = true; + const newToken = await useAuthStore.getState().refreshAccessToken(); + if (newToken) { + config.headers.Authorization = `Bearer ${newToken}`; + return axios.request(config); + } + } + return Promise.reject(error); +}; + +skillsApi.interceptors.request.use(attachToken); +skillsApi.interceptors.response.use((r) => r, handle401); + +mainApi.interceptors.request.use(attachToken); +mainApi.interceptors.response.use((r) => r, handle401); diff --git a/android/didi-app-source/src/api/config.ts b/android/didi-app-source/src/api/config.ts new file mode 100644 index 0000000..7f890f0 --- /dev/null +++ b/android/didi-app-source/src/api/config.ts @@ -0,0 +1,58 @@ +// Adresa platformei DiDi se setează EXCLUSIV la build, prin variabila de mediu +// EXPO_PUBLIC_DIDI_BASE_URL (fișierul .env din rădăcina proiectului — vezi .env.example). +// Toate endpoint-urile (auth prin prefixul /auth al Keycloak, API-uri prin gateway) derivă +// din această singură valoare; nu hardcoda adrese în cod. +const DIDI_BASE_URL = (process.env.EXPO_PUBLIC_DIDI_BASE_URL ?? '').replace(/\/+$/, ''); +if (!DIDI_BASE_URL) { + console.error('[CONFIG] EXPO_PUBLIC_DIDI_BASE_URL nu este setat — vezi .env.example'); +} +const AUTH_BASE_URL = `${DIDI_BASE_URL}/auth`; +const KEYCLOAK_REALM = 'didi-clients'; +const REALM_BASE = `${AUTH_BASE_URL}/realms/${KEYCLOAK_REALM}`; +const OIDC_BASE = `${REALM_BASE}/protocol/openid-connect`; + +export const AUTH_CONFIG = { + AUTH_BASE_URL, + KEYCLOAK_REALM, + CLIENT_ID: 'didi-mobile-app', + REDIRECT_URI: 'didi://callback', + REDIRECT_URI_WEB: typeof window !== 'undefined' && typeof window.location !== 'undefined' + ? `${window.location.origin}/auth/callback` + : 'http://localhost:8081/auth/callback', + SCOPES: 'openid profile email roles', + + AUTH_URL: `${OIDC_BASE}/auth`, + TOKEN_URL: `${OIDC_BASE}/token`, + REGISTER_URL: `${OIDC_BASE}/registrations`, + USERINFO_URL: `${OIDC_BASE}/userinfo`, + LOGOUT_URL: `${OIDC_BASE}/logout`, + PASSWORD_URL: `${REALM_BASE}/account/credentials/password`, +}; + +import { Platform } from 'react-native'; + +export function getRedirectUri(): string { + return Platform.OS === 'web' ? AUTH_CONFIG.REDIRECT_URI_WEB : AUTH_CONFIG.REDIRECT_URI; +} + +export const API_CONFIG = { + MAIN_API: `${DIDI_BASE_URL}/api/`, + MAIN_API_V1: `${DIDI_BASE_URL}/api/v1/`, + SKILLS_API: `${DIDI_BASE_URL}/agent-v3/api/v3/`, + SCRAPER_API: `${DIDI_BASE_URL}/scraper/`, +}; + +export const TIMEOUTS = { + AUTH: 30000, + PIPELINE_TEXT: 120000, + PIPELINE_URL: 120000, + PIPELINE_MEDIA: 300000, + SKILL: 300000, + UPLOAD: 60000, + SCRAPER: 120000, +}; + +export const POLL_CONFIG = { + INTERVAL: 2500, + MAX_DURATION: 600000, +}; diff --git a/android/didi-app-source/src/api/history.ts b/android/didi-app-source/src/api/history.ts new file mode 100644 index 0000000..3f12aa4 --- /dev/null +++ b/android/didi-app-source/src/api/history.ts @@ -0,0 +1,42 @@ +import { skillsApi } from './axios'; +import type { + AnalysisSession, + V3HistoryResponse, + V3Response, +} from '../types/analysis'; + +export async function getHistory( + userId: string, + page: number = 1, + limit: number = 20 +): Promise<{ items: AnalysisSession[]; pagination: V3HistoryResponse['data']['pagination'] }> { + const params = new URLSearchParams({ + user_id: userId, + page: String(page), + limit: String(limit), + }); + const response = await skillsApi.get( + `pipeline/history?${params.toString()}` + ); + const data = response.data?.data || response.data; + return { + items: Array.isArray((data as any)?.items) ? (data as any).items : [], + pagination: (data as any)?.pagination || { page, limit, total: 0, pages: 0 }, + }; +} + +export async function getHistoryDetail( + sessionId: string, + userId: string +): Promise { + const response = await skillsApi.get( + `pipeline/history/${encodeURIComponent(sessionId)}?user_id=${encodeURIComponent(userId)}` + ); + return response.data?.data || response.data as any; +} + +export async function deleteHistoryItem(sessionId: string, userId: string): Promise { + await skillsApi.delete( + `pipeline/history/${encodeURIComponent(sessionId)}?user_id=${encodeURIComponent(userId)}` + ); +} diff --git a/android/didi-app-source/src/api/techniqueDefinitions.ts b/android/didi-app-source/src/api/techniqueDefinitions.ts new file mode 100644 index 0000000..53d5db4 --- /dev/null +++ b/android/didi-app-source/src/api/techniqueDefinitions.ts @@ -0,0 +1,48 @@ +import { skillsApi } from './axios'; +import type { TechniqueDefinition } from '../types/analysis'; + +const CACHE_TTL = 5 * 60 * 1000; // 5 minutes + +let cache: TechniqueDefinition[] | null = null; +let cacheTimestamp = 0; +let pendingRequest: Promise | null = null; + +export async function getTechniqueDefinitions(): Promise { + // Return cache if fresh + if (cache && Date.now() - cacheTimestamp < CACHE_TTL) { + return cache; + } + + // Deduplicate concurrent requests + if (pendingRequest) return pendingRequest; + + pendingRequest = (async () => { + try { + const response = await skillsApi.get<{ success: boolean; data: TechniqueDefinition[] }>( + 'techniques/definitions', + { timeout: 15000 } + ); + const defs = response.data?.data || []; + cache = defs; + cacheTimestamp = Date.now(); + return defs; + } catch { + // If endpoint is down, return cached data or empty — zero errors + return cache || []; + } finally { + pendingRequest = null; + } + })(); + + return pendingRequest; +} + +/** Build a lookup map: technique_name → definition */ +export async function getTechniqueDefinitionsMap(): Promise> { + const defs = await getTechniqueDefinitions(); + const map = new Map(); + for (const d of defs) { + map.set(d.technique_name, d); + } + return map; +} diff --git a/android/didi-app-source/src/components/GlassCard.tsx b/android/didi-app-source/src/components/GlassCard.tsx new file mode 100644 index 0000000..34e71d1 --- /dev/null +++ b/android/didi-app-source/src/components/GlassCard.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { View, TouchableOpacity, Text, StyleSheet, ViewStyle } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { COLORS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS, LAYOUT } from '../theme/colors'; + +interface GlassCardProps { + title: string; + subtitle: string; + iconLabel?: string; + iconColors?: readonly [string, string]; + onPress?: () => void; + style?: ViewStyle; + borderColor?: string; +} + +export default function GlassCard({ + title, + subtitle, + iconLabel, + iconColors, + onPress, + style, + borderColor, +}: GlassCardProps) { + const Wrapper = onPress ? TouchableOpacity : View; + + return ( + + + {iconLabel && ( + + {iconLabel} + + )} + + {title} + {subtitle} + + {onPress && } + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: SPACING.lg, + marginBottom: SPACING.sm + 4, + }, + content: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + iconCircle: { + width: LAYOUT.ICON_CIRCLE_SIZE, + height: LAYOUT.ICON_CIRCLE_SIZE, + borderRadius: RADIUS.full, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + iconText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + }, + textContainer: { + flex: 1, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + subtitle: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + arrow: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xxl, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/components/HistoryCard.tsx b/android/didi-app-source/src/components/HistoryCard.tsx new file mode 100644 index 0000000..60445e1 --- /dev/null +++ b/android/didi-app-source/src/components/HistoryCard.tsx @@ -0,0 +1,309 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; +import { + AlertCircle, Cpu, ShieldCheck, ShieldAlert, AlertOctagon, AlertTriangle, + CheckSquare, HelpCircle, FileText, Image as ImageIcon, Music, Video, Globe, Type, +} from 'lucide-react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { + COLORS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + ACCENT, + getBadgeBgColor, +} from '../theme/colors'; +import type { AnalysisSession } from '../types/analysis'; +import { useTranslation, getTranslation, enumLabel } from '../i18n'; + +interface RunningItemProgress { + progress: number; + completedComponents: string[]; +} + +interface Props { + item: AnalysisSession; + onPress: () => void; + onLongPress: () => void; + progress?: RunningItemProgress; +} + +function formatRelativeTime(dateStr: string): string { + const { t } = getTranslation(); + const date = new Date(dateStr); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMin = Math.floor(diffMs / 60000); + if (diffMin < 1) return t('justNow'); + if (diffMin < 60) return t('minutesAgo', { min: diffMin }); + const diffHr = Math.floor(diffMin / 60); + if (diffHr < 24) return t('hoursAgo', { hr: diffHr }); + const diffDay = Math.floor(diffHr / 24); + if (diffDay < 7) return t('daysAgo', { day: diffDay }); + return date.toLocaleDateString(); +} + +const TYPE_ICON: Record = { + text: Type, + image: ImageIcon, + audio: Music, + video: Video, + url: Globe, +}; + +interface CardSummary { + badgeScore: number; + label: string; + badgeColor: string; + badgeIcon: LucideIcon; +} + +/** Decide what to show on the right side of the card based on session type. */ +function summarizeSession(item: AnalysisSession, t: (k: any) => string, isRo: boolean): CardSummary { + const componentsRun = item.components_run || []; + const isSingle = componentsRun.length === 1; + const single = isSingle ? componentsRun[0] : null; + + if (single === 'domain' || single === 'source_assessment') { + const trust = (item as any).source_trust_score ?? 0; + return { + badgeScore: trust, + label: (item as any).source_verdict + ? enumLabel((item as any).source_verdict) + : t('sourceAssessment'), + badgeColor: trust >= 70 ? '#22c55e' : trust >= 40 ? '#eab308' : trust >= 20 ? '#f97316' : '#ef4444', + badgeIcon: trust >= 70 ? ShieldCheck : trust >= 40 ? ShieldAlert : AlertOctagon, + }; + } + if (single === 'ai_tampered' || single === 'ai-tampered') { + const score = item.risk_score ?? 0; + return { + badgeScore: score, + label: t('aiDetection'), + badgeColor: score >= 80 ? '#ef4444' : score >= 60 ? '#f97316' : score >= 40 ? '#eab308' : '#22c55e', + badgeIcon: score >= 60 ? AlertTriangle : Cpu, + }; + } + if (single === 'techniques') { + const score = item.risk_score ?? 0; + return { + badgeScore: score, + label: t('manipulationTechniques'), + badgeColor: score >= 70 ? '#ef4444' : score >= 40 ? '#f97316' : score >= 20 ? '#eab308' : '#22c55e', + badgeIcon: score >= 70 ? AlertOctagon : score >= 40 ? AlertTriangle : ShieldCheck, + }; + } + if (single === 'claims') { + const score = item.risk_score ?? 0; + return { + badgeScore: score, + label: t('claimsVerification'), + badgeColor: ACCENT.violet, + badgeIcon: CheckSquare, + }; + } + // Pipeline (multi-component or unknown) — use risk_score + risk_category + const score = item.risk_score ?? 0; + return { + badgeScore: score, + label: item.risk_category + ? enumLabel(item.risk_category) + : (item.risk_level ? enumLabel(item.risk_level) : t('analysis')), + badgeColor: score >= 70 ? '#ef4444' + : score >= 50 ? '#f97316' + : score >= 30 ? '#eab308' + : score > 0 ? '#22c55e' + : ACCENT.violet, + badgeIcon: score >= 70 ? AlertOctagon : score >= 30 ? AlertTriangle : score > 0 ? ShieldCheck : HelpCircle, + }; +} + +export default function HistoryCard({ item, onPress, onLongPress, progress }: Props) { + const { t, language } = useTranslation(); + const isRo = language === 'ro'; + const TypeIcon = TYPE_ICON[item.input_type || 'text'] || AlertCircle; + const statusDone = item.status === 'completed'; + const isRunning = item.status === 'running' || item.status === 'pending' || item.status === 'processing'; + + const summary = summarizeSession(item, t, isRo); + + const a11yLabel = [ + item.input_text || item.input_url || t('mediaAnalysis'), + item.created_at ? formatRelativeTime(item.created_at) : null, + statusDone ? `${summary.label}, ${Math.round(summary.badgeScore)}` : item.status, + ] + .filter(Boolean) + .join('. '); + + return ( + + {/* Type icon */} + + + + + {/* Content */} + + + {item.input_text || item.input_url || t('mediaAnalysis')} + + + {item.created_at && ( + {formatRelativeTime(item.created_at)} + )} + {isRunning && ( + + + {progress ? `${Math.round(progress.progress)}%` : item.status} + + + )} + {!statusDone && !isRunning && item.status && ( + + {item.status} + + )} + + {isRunning && progress && progress.progress > 0 && ( + + + + )} + + + {/* Score / state */} + + {statusDone ? ( + <> + + + {Math.round(summary.badgeScore)} + + + + + + {summary.label} + + + + ) : ( + + )} + + + ); +} + +const styles = StyleSheet.create({ + card: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255,255,255,0.025)', + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.06)', + borderRadius: RADIUS.xl, + padding: 14, + marginBottom: SPACING.sm + 2, + }, + typeIconBox: { + width: 38, + height: 38, + borderRadius: RADIUS.lg, + backgroundColor: 'rgba(167,139,250,0.10)', + justifyContent: 'center', + alignItems: 'center', + marginRight: 12, + }, + content: { + flex: 1, + marginRight: SPACING.sm, + }, + inputText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + lineHeight: 19, + marginBottom: 4, + }, + metaRow: { + flexDirection: 'row', + alignItems: 'center', + }, + time: { + color: COLORS.text.muted, + fontSize: FONT_SIZES.xs, + }, + statusBadge: { + marginLeft: SPACING.sm, + paddingHorizontal: SPACING.sm, + paddingVertical: 1, + borderRadius: RADIUS.full, + backgroundColor: 'rgba(255,255,255,0.06)', + }, + statusText: { + color: COLORS.status.yellow, + fontSize: FONT_SIZES.xs - 1, + textTransform: 'capitalize', + }, + progressBarBg: { + width: '100%', + height: 3, + borderRadius: 1.5, + backgroundColor: 'rgba(255,255,255,0.08)', + overflow: 'hidden', + marginTop: SPACING.xs, + }, + progressBarFill: { + height: '100%', + borderRadius: 1.5, + backgroundColor: COLORS.brand.primary, + }, + scoreBox: { + alignItems: 'center', + minWidth: 64, + gap: 4, + }, + scoreCircle: { + width: 44, + height: 44, + borderRadius: 22, + borderWidth: 2, + justifyContent: 'center', + alignItems: 'center', + }, + scoreNum: { + fontSize: 16, + fontWeight: FONT_WEIGHTS.bold, + letterSpacing: -0.4, + }, + catBadge: { + flexDirection: 'row', + alignItems: 'center', + gap: 3, + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 6, + maxWidth: 90, + }, + catText: { + fontSize: 9.5, + fontWeight: FONT_WEIGHTS.semibold, + letterSpacing: 0.4, + textTransform: 'uppercase', + }, +}); diff --git a/android/didi-app-source/src/components/TabSelector.tsx b/android/didi-app-source/src/components/TabSelector.tsx new file mode 100644 index 0000000..816e8bb --- /dev/null +++ b/android/didi-app-source/src/components/TabSelector.tsx @@ -0,0 +1,286 @@ +import React, { useRef, useState, useEffect, useCallback } from 'react'; +import { + View, + TouchableOpacity, + Text, + StyleSheet, + Animated, + Platform, + LayoutChangeEvent, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { + COLORS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, +} from '../theme/colors'; + +interface Tab { + key: string; + label: string; +} + +interface TabSelectorProps { + tabs: Tab[]; + activeTab: string; + onTabChange: (key: string) => void; +} + +const ICON_MAP: Record = { + text: 'T', + image: 'I', + audio: 'A', + video: 'V', + url: 'U', +}; + +const TRACK_PADDING = 3; + +const SPRING_CONFIG = { + tension: 180, + friction: 22, + useNativeDriver: false, +}; + +export default function TabSelector({ + tabs, + activeTab, + onTabChange, +}: TabSelectorProps) { + const tabLayouts = useRef<{ x: number; width: number }[]>([]); + const measuredCount = useRef(0); + const [layoutReady, setLayoutReady] = useState(false); + const isFirstRender = useRef(true); + + // Animated values for the pill + const pillX = useRef(new Animated.Value(0)).current; + const pillWidth = useRef(new Animated.Value(0)).current; + + // Per-tab animated values + const fadeAnims = useRef([]); + const scaleAnims = useRef([]); + + // Initialize per-tab animated values when tabs change + if (fadeAnims.current.length !== tabs.length) { + fadeAnims.current = tabs.map( + (tab, i) => new Animated.Value(tab.key === activeTab ? 1 : 0), + ); + scaleAnims.current = tabs.map( + (tab, i) => new Animated.Value(tab.key === activeTab ? 1.05 : 1), + ); + } + + const activeIndex = tabs.findIndex((t) => t.key === activeTab); + + const handleLayout = useCallback( + (index: number, event: LayoutChangeEvent) => { + const { x, width } = event.nativeEvent.layout; + const isNew = !tabLayouts.current[index]; + tabLayouts.current[index] = { x, width }; + if (isNew) { + measuredCount.current += 1; + } + if (measuredCount.current >= tabs.length && !layoutReady) { + setLayoutReady(true); + } + }, + [tabs.length, layoutReady], + ); + + // Set initial pill position once layout is ready + useEffect(() => { + if (!layoutReady) return; + const layout = tabLayouts.current[activeIndex]; + if (!layout) return; + + if (isFirstRender.current) { + isFirstRender.current = false; + pillX.setValue(layout.x); + pillWidth.setValue(layout.width); + return; + } + + // Animate pill to new position + const animations: Animated.CompositeAnimation[] = [ + Animated.spring(pillX, { ...SPRING_CONFIG, toValue: layout.x }), + Animated.spring(pillWidth, { ...SPRING_CONFIG, toValue: layout.width }), + ]; + + // Animate tab fade and scale + tabs.forEach((tab, i) => { + const isActive = i === activeIndex; + animations.push( + Animated.timing(fadeAnims.current[i], { + toValue: isActive ? 1 : 0, + duration: 200, + useNativeDriver: false, + }), + Animated.spring(scaleAnims.current[i], { + ...SPRING_CONFIG, + toValue: isActive ? 1.05 : 1, + }), + ); + }); + + Animated.parallel(animations).start(); + }, [activeIndex, layoutReady]); + + return ( + + + {/* Animated gradient pill */} + {layoutReady && ( + + + + )} + + {/* Tab items */} + {tabs.map((tab, index) => { + const fadeAnim = fadeAnims.current[index]; + const scaleAnim = scaleAnims.current[index]; + const icon = ICON_MAP[tab.key] || tab.label.charAt(0).toUpperCase(); + + // Interpolate colors based on fade value + const textColor = fadeAnim + ? fadeAnim.interpolate({ + inputRange: [0, 1], + outputRange: [COLORS.text.muted, '#FFFFFF'], + }) + : COLORS.text.muted; + + const badgeBg = fadeAnim + ? fadeAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['rgba(255,255,255,0.06)', 'rgba(255,255,255,0.20)'], + }) + : 'rgba(255,255,255,0.06)'; + + const badgeTextColor = fadeAnim + ? fadeAnim.interpolate({ + inputRange: [0, 1], + outputRange: [COLORS.text.darkMuted, '#FFFFFF'], + }) + : COLORS.text.darkMuted; + + const isActive = tab.key === activeTab; + + return ( + onTabChange(tab.key)} + style={styles.tabTouchable} + onLayout={(e) => handleLayout(index, e)} + accessibilityRole="tab" + accessibilityLabel={tab.label} + accessibilityState={{ selected: isActive }} + > + + + + {icon} + + + + {tab.label} + + + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + container: { + paddingVertical: SPACING.sm, + }, + track: { + flexDirection: 'row', + backgroundColor: COLORS.glass.white5, + borderWidth: 1, + borderColor: COLORS.glass.purple10, + borderRadius: RADIUS.xl, + padding: TRACK_PADDING, + position: 'relative', + }, + pillWrapper: { + position: 'absolute', + top: TRACK_PADDING, + bottom: TRACK_PADDING, + left: 0, + zIndex: 0, + borderRadius: RADIUS.xl - TRACK_PADDING, + ...Platform.select({ + ios: { + shadowColor: '#009198', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.45, + shadowRadius: 8, + }, + android: { + elevation: 6, + }, + }), + }, + pill: { + flex: 1, + borderRadius: RADIUS.xl - TRACK_PADDING, + }, + tabTouchable: { + flex: 1, + zIndex: 1, + }, + tabContent: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: SPACING.sm, + gap: 4, + }, + iconBadge: { + width: 22, + height: 22, + borderRadius: 6, + alignItems: 'center', + justifyContent: 'center', + }, + iconText: { + fontSize: 11, + fontWeight: FONT_WEIGHTS.bold, + }, + tabLabel: { + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/components/UploadConsentModal.tsx b/android/didi-app-source/src/components/UploadConsentModal.tsx new file mode 100644 index 0000000..961c7f0 --- /dev/null +++ b/android/didi-app-source/src/components/UploadConsentModal.tsx @@ -0,0 +1,219 @@ +import React, { useState } from 'react'; +import { Modal, View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, + SHADOWS, +} from '../theme/colors'; +import { useTranslation } from '../i18n'; + +interface UploadConsentModalProps { + visible: boolean; + onAccept: () => void; + onCancel: () => void; +} + +/** + * Explicit consent dialog shown before the first media file upload. + * The user must tick the checkbox before the confirm button is enabled. + */ +export default function UploadConsentModal({ + visible, + onAccept, + onCancel, +}: UploadConsentModalProps) { + const { t } = useTranslation(); + const [checked, setChecked] = useState(false); + + const handleAccept = () => { + if (!checked) return; + setChecked(false); + onAccept(); + }; + + const handleCancel = () => { + setChecked(false); + onCancel(); + }; + + return ( + + + + + {t('uploadConsentTitle')} + + {t('uploadConsentBody')} + + setChecked((c) => !c)} + accessibilityRole="checkbox" + accessibilityState={{ checked }} + accessibilityLabel={t('uploadConsentCheckbox')} + > + + {checked && } + + {t('uploadConsentCheckbox')} + + + + + + {t('cancel')} + + + + + + + {t('uploadConsentAccept')} + + + + + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: COLORS.glass.black50, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: SPACING.lg, + }, + dialog: { + width: '100%', + maxWidth: 420, + backgroundColor: COLORS.bg.dialog, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + ...SHADOWS.dialog, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.sm, + }, + body: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + lineHeight: 20, + marginBottom: SPACING.md, + }, + checkboxRow: { + flexDirection: 'row', + alignItems: 'flex-start', + backgroundColor: COLORS.glass.white5, + borderRadius: RADIUS.lg, + padding: SPACING.md, + marginBottom: SPACING.lg, + }, + checkbox: { + width: 22, + height: 22, + borderRadius: 6, + borderWidth: 2, + borderColor: COLORS.border.secondary, + backgroundColor: COLORS.glass.white10, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.sm + 4, + marginTop: 1, + }, + checkboxChecked: { + borderColor: COLORS.brand.primary, + backgroundColor: COLORS.brand.primary, + }, + checkmark: { + color: COLORS.text.primary, + fontSize: 13, + fontWeight: FONT_WEIGHTS.bold, + }, + checkboxLabel: { + flex: 1, + color: COLORS.text.secondary, + fontSize: FONT_SIZES.xs, + lineHeight: 18, + }, + buttonsRow: { + flexDirection: 'row', + gap: SPACING.md, + }, + cancelButton: { + flex: 1, + backgroundColor: COLORS.glass.white5, + borderWidth: 1, + borderColor: COLORS.border.secondary, + borderRadius: RADIUS.xl, + paddingVertical: SPACING.sm + 4, + alignItems: 'center', + justifyContent: 'center', + }, + cancelText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + acceptButton: { + borderRadius: RADIUS.xl, + paddingVertical: SPACING.sm + 4, + alignItems: 'center', + justifyContent: 'center', + }, + acceptDisabled: { + opacity: 0.5, + }, + acceptText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + acceptTextDisabled: { + color: COLORS.text.muted, + }, +}); diff --git a/android/didi-app-source/src/components/UserProfileWidget.tsx b/android/didi-app-source/src/components/UserProfileWidget.tsx new file mode 100644 index 0000000..ec4f6c1 --- /dev/null +++ b/android/didi-app-source/src/components/UserProfileWidget.tsx @@ -0,0 +1,398 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Animated, + LayoutAnimation, + Platform, + UIManager, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, + getBadgeBgColor, +} from '../theme/colors'; +import { UserProfile } from '../types/auth'; +import { useTranslation } from '../i18n'; + +if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +interface UserProfileWidgetProps { + profile: UserProfile | null; + usageStats: any; + creditsData?: { credits_remained: number; credits_spent: number } | null; +} + +export default function UserProfileWidget({ profile, usageStats, creditsData }: UserProfileWidgetProps) { + const { t } = useTranslation(); + const [expanded, setExpanded] = useState(false); + const navigation = useNavigation>(); + + const initials = profile + ? `${profile.firstName?.[0] || ''}${profile.lastName?.[0] || ''}`.toUpperCase() + : '?'; + + const plan = usageStats?.plan as any; + const sub = usageStats?.subscription as any; + + const planName = plan?.name || 'Free'; + + // Credits from auth/credits endpoint (authoritative source) + const credits = creditsData?.credits_remained ?? profile?.creditsRemained ?? 0; + const creditSpent = creditsData?.credits_spent ?? profile?.creditsSpent ?? 0; + const creditTotal = credits + creditSpent; + + const toggleExpand = () => { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + setExpanded(!expanded); + }; + + // Credit color based on remaining + const creditColor = + credits < 5 ? COLORS.status.red : + credits <= 10 ? COLORS.status.yellow : + COLORS.status.green; + + return ( + + {/* Collapsed Bar */} + + + {initials} + + + + {profile ? profile.firstName : '...'} + + + {t('creditsLabel', { count: credits })} + + + {planName.toUpperCase()} + + + {expanded ? '▴' : '▾'} + + + {/* Expanded Dropdown */} + {expanded && ( + + {/* Section 1: User Info */} + + + {initials} + + + + {profile ? `${profile.firstName} ${profile.lastName}` : t('loading')} + + {profile?.email || ''} + + + {planName} + + {usageStats && ( + + ${plan?.priceAmount ?? plan?.price_amount ?? 0}{t('perMonth')} + + )} + + + {(sub?.isActive ?? sub?.is_active) ? t('active') : t('inactive')} + + + + + + + + {/* Section 2: Credits + Limits */} + + {/* Credits Column */} + + {t('credits')} + + {t('remaining')} + {credits} + + + + {/* Limits Column */} + + {t('planLimits')} + + {t('creditsCycle')} + {creditTotal || '—'} + + + {t('maxImages')} + {plan?.maxImages ?? plan?.max_images ?? '—'} + + + {t('videoMinutes')} + {plan?.maxVideoMinutes ?? plan?.max_video_minutes ?? '—'} + + + {t('storage')} + {plan?.storageLimitGb ?? plan?.storage_limit_gb ?? '—'} GB + + + + + + + {/* Section 3: Credit Costs + Settings */} + + + Text: {usageStats?.costs?.text ?? 1}cr + | + URL: {usageStats?.costs?.url ?? 1}cr + | + Img: {usageStats?.costs?.image ?? 2}cr + | + Audio: {usageStats?.costs?.audio ?? 3}cr + | + Video: {usageStats?.costs?.video ?? 5}cr + + navigation.navigate('Settings')} + style={styles.settingsButton} + > + {t('settings')} + + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + marginBottom: SPACING.lg, + }, + // Trigger Bar + triggerBar: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + paddingVertical: SPACING.sm + 4, + paddingHorizontal: SPACING.md, + minHeight: 52, + }, + avatarSmall: { + width: 32, + height: 32, + borderRadius: RADIUS.full, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.sm, + }, + avatarSmallText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, + triggerName: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + marginRight: SPACING.sm, + flexShrink: 1, + }, + triggerCredits: { + color: COLORS.brand.accentCyan, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + marginRight: SPACING.sm, + }, + planBadge: { + backgroundColor: COLORS.glass.cyan10, + borderWidth: 1, + borderColor: COLORS.glass.cyan20, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.sm, + paddingVertical: 2, + marginRight: SPACING.sm, + }, + planBadgeText: { + color: COLORS.brand.accentCyan, + fontSize: 10, + fontWeight: FONT_WEIGHTS.bold, + }, + arrow: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.sm, + marginLeft: 'auto', + }, + // Dropdown + dropdown: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginTop: SPACING.sm, + }, + // Section 1 + userInfoSection: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + avatarLarge: { + width: LAYOUT.AVATAR_SIZE_LARGE, + height: LAYOUT.AVATAR_SIZE_LARGE, + borderRadius: RADIUS.full, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + avatarLargeText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + userInfoText: { + flex: 1, + }, + userName: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + userEmail: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + planRow: { + flexDirection: 'row', + alignItems: 'center', + marginTop: SPACING.sm, + flexWrap: 'wrap', + gap: SPACING.sm, + }, + planBadgeLarge: { + backgroundColor: COLORS.glass.cyan10, + borderWidth: 1, + borderColor: COLORS.glass.cyan20, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.sm + 4, + paddingVertical: SPACING.xs, + }, + planBadgeLargeText: { + color: COLORS.brand.accentCyan, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, + planPrice: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.xs, + }, + statusDot: { + width: 8, + height: 8, + borderRadius: RADIUS.full, + }, + statusText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.xs, + }, + divider: { + height: 1, + backgroundColor: COLORS.border.accent, + marginVertical: SPACING.md, + }, + // Section 2 + columnsRow: { + flexDirection: 'row', + gap: SPACING.md, + }, + column: { + flex: 1, + }, + columnTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.sm, + }, + statRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: SPACING.xs + 2, + }, + statLabel: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + }, + statValue: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, + // Section 3 + footerRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + minHeight: 40, + }, + costsRow: { + flexDirection: 'row', + alignItems: 'center', + flexWrap: 'wrap', + flex: 1, + gap: 4, + }, + costItem: { + color: COLORS.text.hint, + fontSize: 10, + }, + costDivider: { + color: COLORS.text.muted, + fontSize: 10, + }, + settingsButton: { + backgroundColor: COLORS.glass.purple10, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.sm, + marginLeft: SPACING.sm, + }, + settingsText: { + color: COLORS.brand.primary, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/components/editorial/ActionCallout.tsx b/android/didi-app-source/src/components/editorial/ActionCallout.tsx new file mode 100644 index 0000000..6b45995 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/ActionCallout.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import type { LucideIcon } from 'lucide-react-native'; +import { COLORS, SPACING, RADIUS, FONT_WEIGHTS } from '../../theme/colors'; + +interface Props { + accent: string; + icon: LucideIcon; + /** Tiny uppercase tag (e.g. "DO NOT SHARE", "READ CRITICALLY"). */ + eyebrow: string; + /** Bold one-liner action prompt. */ + headline: string; + /** Optional descriptive paragraph. */ + body?: string; +} + +function withAlpha(hex: string, alpha: number) { + const cleaned = hex.replace('#', ''); + const r = parseInt(cleaned.slice(0, 2), 16); + const g = parseInt(cleaned.slice(2, 4), 16); + const b = parseInt(cleaned.slice(4, 6), 16); + return `rgba(${r},${g},${b},${alpha})`; +} + +export default function ActionCallout({ accent, icon: Icon, eyebrow, headline, body }: Props) { + return ( + + + + + + + + {eyebrow} + {headline} + {body ? {body} : null} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + borderWidth: 1, + borderRadius: RADIUS.xxl, + padding: 20, + overflow: 'hidden', + marginBottom: SPACING.md, + }, + row: { + flexDirection: 'row', + gap: 16, + alignItems: 'flex-start', + }, + iconBox: { + width: 42, + height: 42, + borderRadius: 12, + justifyContent: 'center', + alignItems: 'center', + flexShrink: 0, + }, + body: { + flex: 1, + gap: 4, + }, + eyebrow: { + fontSize: 11, + fontWeight: FONT_WEIGHTS.bold, + letterSpacing: 1.3, + textTransform: 'uppercase', + }, + headline: { + fontSize: 18, + fontWeight: FONT_WEIGHTS.semibold, + color: '#fff', + letterSpacing: -0.4, + lineHeight: 24, + }, + text: { + marginTop: 4, + fontSize: 14, + lineHeight: 21, + color: 'rgba(255,255,255,0.75)', + }, +}); diff --git a/android/didi-app-source/src/components/editorial/Chip.tsx b/android/didi-app-source/src/components/editorial/Chip.tsx new file mode 100644 index 0000000..a995cb6 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/Chip.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { ACCENT, type FindingAccent, FONT_WEIGHTS } from '../../theme/colors'; + +interface ChipProps { + variant?: FindingAccent | 'default'; + children: React.ReactNode; + /** Optional leading icon node (e.g. a Lucide ). */ + leading?: React.ReactNode; +} + +const VARIANT_FG: Record = { + ...ACCENT, + default: ACCENT.violet, +}; + +function withAlpha(hex: string, alpha: number) { + const cleaned = hex.replace('#', ''); + const r = parseInt(cleaned.slice(0, 2), 16); + const g = parseInt(cleaned.slice(2, 4), 16); + const b = parseInt(cleaned.slice(4, 6), 16); + return `rgba(${r},${g},${b},${alpha})`; +} + +export function Chip({ variant = 'default', children, leading }: ChipProps) { + const fg = VARIANT_FG[variant]; + return ( + + {leading ? {leading} : null} + {children} + + ); +} + +interface RowProps { + children: React.ReactNode; + style?: any; +} + +export function ChipsRow({ children, style }: RowProps) { + return {children}; +} + +const styles = StyleSheet.create({ + chip: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 5, + paddingHorizontal: 12, + borderRadius: 999, + }, + text: { + fontSize: 12, + fontWeight: FONT_WEIGHTS.medium, + letterSpacing: -0.05, + }, + row: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 6, + }, +}); diff --git a/android/didi-app-source/src/components/editorial/CollapseButton.tsx b/android/didi-app-source/src/components/editorial/CollapseButton.tsx new file mode 100644 index 0000000..7607b54 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/CollapseButton.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { TouchableOpacity, View, Text, StyleSheet } from 'react-native'; +import { ChevronDown } from 'lucide-react-native'; +import { COLORS, SPACING, RADIUS, FONT_WEIGHTS } from '../../theme/colors'; + +interface Props { + onPress: () => void; + expanded: boolean; + label: string; + /** Optional leading icon node (defaults to none). */ + leading?: React.ReactNode; +} + +export default function CollapseButton({ onPress, expanded, label, leading }: Props) { + return ( + + + {leading} + {label} + + + + + + ); +} + +const styles = StyleSheet.create({ + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 12, + paddingHorizontal: 18, + backgroundColor: 'rgba(255,255,255,0.022)', + borderRadius: RADIUS.xl, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.05)', + marginBottom: SPACING.sm, + }, + left: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + flex: 1, + }, + label: { + fontSize: 13, + fontWeight: FONT_WEIGHTS.medium, + color: 'rgba(255,255,255,0.75)', + letterSpacing: -0.05, + }, + chevron: { + transform: [{ rotate: '0deg' }], + }, + chevronExpanded: { + transform: [{ rotate: '180deg' }], + }, +}); diff --git a/android/didi-app-source/src/components/editorial/FindingCard.tsx b/android/didi-app-source/src/components/editorial/FindingCard.tsx new file mode 100644 index 0000000..fb90e20 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/FindingCard.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { COLORS, SPACING, RADIUS, EDITORIAL, ACCENT, type FindingAccent } from '../../theme/colors'; + +interface Props { + icon: LucideIcon; + accent?: FindingAccent; + /** When true, render a slightly larger emphasized variant. */ + dominant?: boolean; + eyebrow?: React.ReactNode; + headline: React.ReactNode; + /** Optional pull-quote (Merriweather italic with left rule). */ + quote?: React.ReactNode; + /** Optional source row (icons + links + separators). */ + source?: React.ReactNode; +} + +function withAlpha(hex: string, alpha: number) { + const cleaned = hex.replace('#', ''); + const r = parseInt(cleaned.slice(0, 2), 16); + const g = parseInt(cleaned.slice(2, 4), 16); + const b = parseInt(cleaned.slice(4, 6), 16); + return `rgba(${r},${g},${b},${alpha})`; +} + +export default function FindingCard({ + icon: Icon, accent = 'critical', dominant, eyebrow, headline, quote, source, +}: Props) { + const hex = ACCENT[accent]; + const iconSize = dominant ? 20 : 18; + const boxSize = dominant ? 38 : 34; + const boxRadius = dominant ? 11 : 10; + const borderColor = dominant + ? withAlpha(hex, 0.22) + : 'rgba(255,255,255,0.08)'; + + return ( + + + + + + {eyebrow ? ( + {eyebrow} + ) : null} + + {headline} + + {quote ? ( + + + {quote} + + ) : null} + {source ? {source} : null} + + + ); +} + +const styles = StyleSheet.create({ + card: { + flexDirection: 'row', + gap: 14, + paddingVertical: 18, + paddingHorizontal: 18, + borderWidth: 1, + borderRadius: RADIUS.xxl, + backgroundColor: 'rgba(255,255,255,0.025)', + marginBottom: SPACING.sm + 4, + }, + dominant: { + paddingVertical: 20, + paddingHorizontal: 20, + }, + iconBox: { + justifyContent: 'center', + alignItems: 'center', + flexShrink: 0, + }, + body: { + flex: 1, + minWidth: 0, + gap: 8, + }, + eyebrow: { + fontSize: 10.5, + letterSpacing: 1.3, + fontWeight: '600', + color: 'rgba(255,255,255,0.45)', + textTransform: 'uppercase', + }, + headline: EDITORIAL.findingHeadline, + headlineDominant: EDITORIAL.findingHeadlineDominant, + quoteWrap: { + flexDirection: 'row', + gap: 12, + }, + quoteRule: { + width: 2, + backgroundColor: 'rgba(255,255,255,0.14)', + borderRadius: 1, + }, + quote: { + ...EDITORIAL.quote, + flex: 1, + }, + sourceRow: { + flexDirection: 'row', + flexWrap: 'wrap', + alignItems: 'center', + gap: 6, + }, +}); diff --git a/android/didi-app-source/src/components/editorial/HeadlineCard.tsx b/android/didi-app-source/src/components/editorial/HeadlineCard.tsx new file mode 100644 index 0000000..a314425 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/HeadlineCard.tsx @@ -0,0 +1,246 @@ +import React, { useEffect } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import Svg, { Circle } from 'react-native-svg'; +import Animated, { + useSharedValue, + useAnimatedProps, + withDelay, + withTiming, + Easing, +} from 'react-native-reanimated'; +import { LinearGradient } from 'expo-linear-gradient'; +import type { LucideIcon } from 'lucide-react-native'; +import { COLORS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS, EDITORIAL } from '../../theme/colors'; + +const AnimatedCircle = Animated.createAnimatedComponent(Circle); + +const GAUGE_SIZE = 132; +const STROKE_WIDTH = 7; +const RADIUS_PX = (GAUGE_SIZE - STROKE_WIDTH) / 2; +const CIRCUMFERENCE = 2 * Math.PI * RADIUS_PX; + +interface Props { + /** Hex color used for accent on icon-box, gauge stroke and gradient bg. */ + accent: string; + /** Score 0–100 displayed inside the gauge. */ + score: number; + /** Optional secondary line for gauge center, e.g. "/ 100 RISK". */ + scoreLabel?: string; + /** Small uppercase label above the category title. */ + eyebrow: string; + /** Lucide icon rendered in the small accent square. */ + icon: LucideIcon; + /** Big category name. */ + category: string; + /** Inline descriptor next to category (already composed with separators). */ + descriptor?: React.ReactNode; + /** Optional TL;DR paragraph (Merriweather italic). */ + tldr?: React.ReactNode; +} + +function withAlpha(hex: string, alpha: number): string { + const cleaned = hex.replace('#', ''); + const r = parseInt(cleaned.slice(0, 2), 16); + const g = parseInt(cleaned.slice(2, 4), 16); + const b = parseInt(cleaned.slice(4, 6), 16); + return `rgba(${r},${g},${b},${alpha})`; +} + +export default function HeadlineCard({ + accent, score, scoreLabel, eyebrow, icon: Icon, category, descriptor, tldr, +}: Props) { + const offset = useSharedValue(CIRCUMFERENCE); + const animatedScore = useSharedValue(0); + + useEffect(() => { + const target = CIRCUMFERENCE * (1 - Math.min(1, Math.max(0, score / 100))); + offset.value = withDelay(200, withTiming(target, { + duration: 1100, + easing: Easing.out(Easing.cubic), + })); + animatedScore.value = withDelay(200, withTiming(score, { + duration: 1100, + easing: Easing.out(Easing.cubic), + })); + }, [score, offset, animatedScore]); + + const animatedCircleProps = useAnimatedProps(() => ({ + strokeDashoffset: offset.value, + })); + + const [displayScore, setDisplayScore] = React.useState(0); + useEffect(() => { + const interval = setInterval(() => { + setDisplayScore(Math.round(animatedScore.value)); + }, 16); + return () => clearInterval(interval); + }, [animatedScore]); + + return ( + + + + + + + {/* GAUGE — centered on top */} + + + + + + + {displayScore} + {scoreLabel ? {scoreLabel} : null} + + + + {/* CONTENT — full-width below */} + + + + {eyebrow} + + + + + + + + {category} + + + {descriptor ? {descriptor} : null} + {tldr ? {tldr} : null} + + + ); +} + +const styles = StyleSheet.create({ + card: { + borderWidth: 1, + borderRadius: RADIUS.xxl, + paddingVertical: 28, + paddingHorizontal: 24, + overflow: 'hidden', + backgroundColor: 'rgba(255,255,255,0.025)', + marginBottom: SPACING.md, + alignItems: 'center', + }, + glow: { + position: 'absolute', + top: -120, + right: -120, + width: 280, + height: 280, + borderRadius: 999, + opacity: 0.65, + }, + gaugeWrap: { + width: GAUGE_SIZE, + height: GAUGE_SIZE, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 22, + }, + gaugeCenter: { + position: 'absolute', + top: 0, left: 0, right: 0, bottom: 0, + justifyContent: 'center', + alignItems: 'center', + }, + gaugeScore: { + fontSize: 44, + fontWeight: FONT_WEIGHTS.semibold, + color: '#fff', + lineHeight: 46, + letterSpacing: -1.2, + }, + gaugeLabel: { + marginTop: 4, + fontSize: 9.5, + letterSpacing: 1.2, + color: 'rgba(255,255,255,0.32)', + fontWeight: FONT_WEIGHTS.medium, + textTransform: 'uppercase', + }, + content: { + width: '100%', + alignItems: 'center', + }, + eyebrowRow: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 12, + gap: 10, + width: '100%', + justifyContent: 'center', + }, + eyebrowRule: { + width: 18, + height: 1, + backgroundColor: 'rgba(255,255,255,0.28)', + }, + eyebrow: { + ...EDITORIAL.eyebrow, + flexShrink: 1, + textAlign: 'center', + }, + categoryRow: { + flexDirection: 'row', + alignItems: 'center', + flexWrap: 'wrap', + gap: 10, + marginBottom: 8, + justifyContent: 'center', + }, + iconBox: { + width: 30, + height: 30, + borderRadius: 9, + justifyContent: 'center', + alignItems: 'center', + }, + category: { + ...EDITORIAL.category, + flexShrink: 1, + textAlign: 'center', + }, + descriptor: { + ...EDITORIAL.descriptor, + textAlign: 'center', + }, + tldr: { + ...EDITORIAL.tldr, + marginTop: 14, + textAlign: 'center', + }, +}); diff --git a/android/didi-app-source/src/components/editorial/InputPreview.tsx b/android/didi-app-source/src/components/editorial/InputPreview.tsx new file mode 100644 index 0000000..ea05b75 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/InputPreview.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { View, Text, StyleSheet, ScrollView } from 'react-native'; +import { useTranslation } from '../../i18n'; +import { SPACING, RADIUS, FONT_WEIGHTS } from '../../theme/colors'; + +interface Props { + inputType: string; + text: string | null | undefined; +} + +function toParagraphs(raw: string): string[] { + return raw + .split(/\n{2,}/) + .map(p => p.replace(/\s*\n\s*/g, ' ').trim()) + .filter(Boolean); +} + +export default function InputPreview({ inputType, text }: Props) { + const { t } = useTranslation(); + if (!text) return null; + const paragraphs = toParagraphs(text); + return ( + + {t('inputLabel', { type: inputType })} + + {paragraphs.map((p, i) => ( + 0 && { marginTop: 10 }]}>{p} + ))} + + + ); +} + +const styles = StyleSheet.create({ + block: { + paddingVertical: 14, + paddingHorizontal: 18, + backgroundColor: 'rgba(255,255,255,0.018)', + borderRadius: RADIUS.xl, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.04)', + marginBottom: SPACING.md, + }, + label: { + fontSize: 10.5, + letterSpacing: 1.6, + fontWeight: FONT_WEIGHTS.semibold, + color: 'rgba(255,255,255,0.45)', + textTransform: 'uppercase', + marginBottom: 6, + }, + scroll: { + maxHeight: 220, + }, + body: { + fontSize: 13, + lineHeight: 21, + color: 'rgba(255,255,255,0.7)', + }, +}); diff --git a/android/didi-app-source/src/components/editorial/ScoreLine.tsx b/android/didi-app-source/src/components/editorial/ScoreLine.tsx new file mode 100644 index 0000000..b6d394e --- /dev/null +++ b/android/didi-app-source/src/components/editorial/ScoreLine.tsx @@ -0,0 +1,83 @@ +import React, { useEffect } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import Animated, { useSharedValue, useAnimatedStyle, withDelay, withTiming, Easing } from 'react-native-reanimated'; +import { FONT_WEIGHTS } from '../../theme/colors'; + +interface Props { + name: string; + score: number; + /** Hex color for the bar fill. */ + color: string; + /** Stagger delay (ms) for the bar animation. */ + delay?: number; +} + +export default function ScoreLine({ name, score, color, delay = 0 }: Props) { + const width = useSharedValue(0); + + useEffect(() => { + width.value = withDelay(delay, withTiming(Math.min(score, 100), { + duration: 900, + easing: Easing.out(Easing.cubic), + })); + }, [score, delay, width]); + + const fillStyle = useAnimatedStyle(() => ({ + width: `${width.value}%`, + })); + + return ( + + {name} + + + + {score} + + ); +} + +interface BlockProps { + children: React.ReactNode; +} + +export function ScoresBlock({ children }: BlockProps) { + return {children}; +} + +const styles = StyleSheet.create({ + block: { + gap: 10, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + name: { + fontSize: 13, + color: 'rgba(255,255,255,0.7)', + width: 100, + fontWeight: FONT_WEIGHTS.regular, + letterSpacing: -0.05, + }, + barBg: { + flex: 1, + height: 4, + backgroundColor: 'rgba(255,255,255,0.06)', + borderRadius: 2, + overflow: 'hidden', + }, + barFill: { + height: '100%', + borderRadius: 2, + }, + value: { + fontSize: 13, + fontWeight: FONT_WEIGHTS.medium, + color: 'rgba(255,255,255,0.85)', + width: 40, + textAlign: 'right', + letterSpacing: -0.1, + }, +}); diff --git a/android/didi-app-source/src/components/editorial/SectionDivider.tsx b/android/didi-app-source/src/components/editorial/SectionDivider.tsx new file mode 100644 index 0000000..80c7223 --- /dev/null +++ b/android/didi-app-source/src/components/editorial/SectionDivider.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { EDITORIAL, SPACING } from '../../theme/colors'; + +interface Props { + label: string; + count?: React.ReactNode; +} + +export default function SectionDivider({ label, count }: Props) { + return ( + + {label} + + {count != null ? {count} : null} + + ); +} + +const styles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + paddingVertical: 8, + paddingHorizontal: 4, + marginTop: SPACING.sm, + }, + label: EDITORIAL.dividerLabel, + line: { + flex: 1, + height: 1, + backgroundColor: 'rgba(255,255,255,0.08)', + }, + count: EDITORIAL.dividerCount, +}); diff --git a/android/didi-app-source/src/components/editorial/StatBlock.tsx b/android/didi-app-source/src/components/editorial/StatBlock.tsx new file mode 100644 index 0000000..5824b5c --- /dev/null +++ b/android/didi-app-source/src/components/editorial/StatBlock.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { SPACING, RADIUS } from '../../theme/colors'; + +interface Props { + children: React.ReactNode; +} + +/** A subtle container holding chips and/or scores, used right under the HeadlineCard. */ +export default function StatBlock({ children }: Props) { + return {children}; +} + +const styles = StyleSheet.create({ + block: { + paddingVertical: 18, + paddingHorizontal: 20, + backgroundColor: 'rgba(255,255,255,0.018)', + borderRadius: RADIUS.xl, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.04)', + gap: 14, + marginBottom: SPACING.sm + 4, + }, +}); diff --git a/android/didi-app-source/src/components/editorial/index.ts b/android/didi-app-source/src/components/editorial/index.ts new file mode 100644 index 0000000..d61aa2d --- /dev/null +++ b/android/didi-app-source/src/components/editorial/index.ts @@ -0,0 +1,9 @@ +export { default as HeadlineCard } from './HeadlineCard'; +export { default as FindingCard } from './FindingCard'; +export { default as SectionDivider } from './SectionDivider'; +export { default as ActionCallout } from './ActionCallout'; +export { default as InputPreview } from './InputPreview'; +export { default as CollapseButton } from './CollapseButton'; +export { default as StatBlock } from './StatBlock'; +export { default as ScoreLine, ScoresBlock } from './ScoreLine'; +export { Chip, ChipsRow } from './Chip'; diff --git a/android/didi-app-source/src/components/results/DidYouKnowCard.tsx b/android/didi-app-source/src/components/results/DidYouKnowCard.tsx new file mode 100644 index 0000000..c691cc0 --- /dev/null +++ b/android/didi-app-source/src/components/results/DidYouKnowCard.tsx @@ -0,0 +1,114 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { View, Text, StyleSheet, Animated } from 'react-native'; +import { useTranslation } from '../../i18n'; +import { + COLORS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../../theme/colors'; +import type { TechniqueDefinition } from '../../types/analysis'; +import { formatTechniqueName } from '../../utils/formatTechniqueName'; + +interface Props { + definitions: TechniqueDefinition[]; +} + +const CYCLE_INTERVAL = 7000; + +export default function DidYouKnowCard({ definitions }: Props) { + const { t } = useTranslation(); + const [index, setIndex] = useState(() => + definitions.length > 0 ? Math.floor(Math.random() * definitions.length) : 0 + ); + const fadeAnim = useRef(new Animated.Value(1)).current; + + useEffect(() => { + if (definitions.length <= 1) return; + + const timer = setInterval(() => { + // Fade out + Animated.timing(fadeAnim, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(() => { + // Pick a new random index (different from current) + setIndex((prev) => { + let next = Math.floor(Math.random() * definitions.length); + if (next === prev && definitions.length > 1) { + next = (next + 1) % definitions.length; + } + return next; + }); + // Fade in + Animated.timing(fadeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }).start(); + }); + }, CYCLE_INTERVAL); + + return () => clearInterval(timer); + }, [definitions.length, fadeAnim]); + + if (definitions.length === 0) return null; + + const def = definitions[index]; + if (!def) return null; + + return ( + + {t('didYouKnow')} + + + {formatTechniqueName(def.technique_name)} + + + {def.dimension} — {def.subdimension} + + + {def.description_en} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.glass.purple10, + borderWidth: 1, + borderColor: COLORS.glass.purple25, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginBottom: SPACING.md, + }, + title: { + color: COLORS.brand.primaryLight, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.sm, + textTransform: 'uppercase', + letterSpacing: 1, + }, + techniqueName: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.xs, + }, + dimensionText: { + color: COLORS.brand.primaryLight, + fontSize: FONT_SIZES.xs, + marginBottom: SPACING.sm, + }, + description: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + lineHeight: 20, + }, +}); diff --git a/android/didi-app-source/src/components/sections/AiResults.tsx b/android/didi-app-source/src/components/sections/AiResults.tsx new file mode 100644 index 0000000..7fec755 --- /dev/null +++ b/android/didi-app-source/src/components/sections/AiResults.tsx @@ -0,0 +1,280 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { + Cpu, ShieldCheck, ShieldAlert, AlertOctagon, AlertTriangle, AlertCircle, + FileText, Image as ImageIcon, Sparkles, Plus, +} from 'lucide-react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { + HeadlineCard, FindingCard, SectionDivider, StatBlock, ChipsRow, Chip, CollapseButton, +} from '../editorial'; +import type { FindingAccent } from '../../theme/colors'; +import type { V3AiTamperedResult, V3AiIndicator } from '../../types/analysis'; +import { localized, useTranslation, enumLabel } from '../../i18n'; + +const DEFAULT_VISIBLE_INDICATORS = 5; + +const probAccent = (p: number) => + p >= 80 ? '#ef4444' : p >= 60 ? '#f97316' : p >= 40 ? '#eab308' : '#22c55e'; + +const probIcon = (p: number): LucideIcon => { + if (p >= 80) return AlertOctagon; + if (p >= 60) return AlertTriangle; + if (p >= 40) return Cpu; + return ShieldCheck; +}; + +const indicatorAccent = (confidence: number): FindingAccent => { + if (confidence >= 80) return 'critical'; + if (confidence >= 60) return 'warning'; + if (confidence >= 40) return 'info'; + return 'neutral'; +}; + +const VERDICT_LABELS: Record = { + LIKELY_AI: { ro: 'Probabil AI', en: 'Likely AI' }, + POSSIBLY_AI: { ro: 'Posibil AI', en: 'Possibly AI' }, + UNLIKELY_AI: { ro: 'Improbabil AI', en: 'Unlikely AI' }, + LIKELY_HUMAN: { ro: 'Probabil uman', en: 'Likely human' }, + HUMAN: { ro: 'Uman', en: 'Human' }, + MIXED: { ro: 'Mixt', en: 'Mixed' }, +}; + +const CATEGORY_LABELS: Record = { + T1: { ro: 'Stil de scriere', en: 'Writing style' }, + T2: { ro: 'Tipare conținut', en: 'Content patterns' }, + T3: { ro: 'Analiză structurală', en: 'Structural analysis' }, + T4: { ro: 'Markeri statistici', en: 'Statistical markers' }, + T5: { ro: 'Semnale explicite', en: 'Explicit signals' }, +}; + +export interface AiDisplayResult { + verdict: string; + ai_probability: number; + disclosure_detected?: boolean; + disclosure_explicit?: boolean; + disclosure_text?: string | null; + indicators_found?: string[]; + indicators_detected?: V3AiIndicator[]; + categories_affected?: string[]; + coupling_context?: V3AiTamperedResult['coupling_context']; + image_indicators?: string[]; + image_evidence?: string; + model_used?: string; + transcript?: string; +} + +export function toAiDisplayResult( + r: V3AiTamperedResult, + ctx: { inputType?: string; inputText?: string | null } = {}, +): AiDisplayResult { + const out: AiDisplayResult = { + verdict: r.verdict, + ai_probability: r.ai_probability, + disclosure_detected: r.disclosure_detected, + disclosure_explicit: r.disclosure_explicit, + disclosure_text: r.disclosure_text, + }; + if (r.indicators_detected && r.indicators_detected.length > 0) { + out.indicators_detected = r.indicators_detected; + out.categories_affected = r.categories_affected; + out.coupling_context = r.coupling_context; + } + const img = r.image_analysis as any; + if (img?.indicators && img.indicators.length > 0) { + out.image_indicators = img.indicators; + out.image_evidence = img.evidence; + out.model_used = img.model_used; + } + if (ctx.inputText && (ctx.inputType === 'audio' || ctx.inputType === 'video')) { + out.transcript = ctx.inputText; + } + return out; +} + +interface Props { + result: AiDisplayResult; +} + +export default function AiResults({ result }: Props) { + const { t, language } = useTranslation(); + const isRo = language === 'ro'; + const [indicatorsExpand, setIndicatorsExpand] = useState(false); + + const probability = result.ai_probability || 0; + const verdictKey = (result.verdict || '').toUpperCase(); + const verdictText = VERDICT_LABELS[verdictKey]?.[isRo ? 'ro' : 'en'] + || enumLabel(result.verdict || ''); + const accent = probAccent(probability); + const Icon = probIcon(probability); + + const indCount = result.indicators_detected?.length + || result.indicators_found?.length + || result.image_indicators?.length + || 0; + const disclosureNote = result.disclosure_detected === true + ? (isRo ? ' cu declarație AI' : ' with AI disclosure') + : result.disclosure_detected === false + ? (isRo ? ' fără declarație AI' : ' without AI disclosure') + : ''; + const tldr = isRo + ? `Probabilitate ${probability}% de generare AI${disclosureNote}${indCount > 0 ? ` · ${indCount} ${indCount === 1 ? 'indicator detectat' : 'indicatori detectați'}` : ''}.` + : `${probability}% likelihood of AI generation${disclosureNote}${indCount > 0 ? ` · ${indCount} ${indCount === 1 ? 'indicator detected' : 'indicators detected'}` : ''}.`; + + const totalIndicators = result.indicators_detected?.length ?? 0; + const visibleCount = indicatorsExpand ? totalIndicators : Math.min(DEFAULT_VISIBLE_INDICATORS, totalIndicators); + const visibleIndicators = result.indicators_detected?.slice(0, visibleCount) ?? []; + const remainingIndicators = totalIndicators - visibleCount; + + const descriptorBits: string[] = []; + const confidence = (result.coupling_context as any)?.for_verdict?.confidence_level; + if (confidence) descriptorBits.push(`${isRo ? 'încredere' : 'confidence'} ${enumLabel(confidence).toLowerCase()}`); + if (result.disclosure_detected === true) descriptorBits.push(isRo ? 'cu declarație AI' : 'with AI disclosure'); + if (result.disclosure_detected === false) descriptorBits.push(isRo ? 'fără declarație AI' : 'no AI disclosure'); + + return ( + + + + {(result.coupling_context || (result.categories_affected && result.categories_affected.length > 0) || result.model_used) ? ( + + + {(result.coupling_context as any)?.for_verdict?.undisclosed_ai && ( + {isRo ? 'Conținut AI nedeclarat' : 'Undisclosed AI'} + )} + {(result.coupling_context as any)?.for_verdict?.needs_manual_review && ( + {isRo ? 'Necesită verificare manuală' : 'Needs manual review'} + )} + {(result.categories_affected ?? []).map(cat => { + const cl = CATEGORY_LABELS[cat]?.[isRo ? 'ro' : 'en'] || cat; + return {cat} · {cl}; + })} + {result.model_used ? {result.model_used} : null} + + + ) : null} + + {result.disclosure_detected !== undefined && ( + + )} + + {result.image_evidence && ( + + )} + + {result.transcript && ( + 320 ? result.transcript.slice(0, 320) + '…' : result.transcript} + /> + )} + + {result.indicators_found && result.indicators_found.length > 0 && ( + <> + + {result.indicators_found.map((ind, i) => ( + + ))} + + )} + + {result.image_indicators && result.image_indicators.length > 0 && ( + <> + + {result.image_indicators.map((ind, i) => ( + + ))} + + )} + + {totalIndicators > 0 && ( + <> + + {visibleIndicators.map(ind => { + const catLabel = CATEGORY_LABELS[ind.category]?.[isRo ? 'ro' : 'en'] || ind.category; + const eyebrowParts = [ind.category, catLabel, `${isRo ? 'încredere' : 'confidence'} ${ind.confidence}%`]; + return ( + + ); + })} + {remainingIndicators > 0 && ( + setIndicatorsExpand(true)} + expanded={false} + leading={} + label={ + isRo + ? `Vezi celelalți ${remainingIndicators} ${remainingIndicators === 1 ? 'indicator' : 'indicatori'}` + : `Show ${remainingIndicators} more ${remainingIndicators === 1 ? 'indicator' : 'indicators'}` + } + /> + )} + {indicatorsExpand && totalIndicators > DEFAULT_VISIBLE_INDICATORS && ( + setIndicatorsExpand(false)} + expanded={true} + label={isRo ? 'Ascunde' : 'Hide'} + /> + )} + + )} + + {(result.indicators_found?.length === 0 + && !result.indicators_detected + && !result.image_indicators?.length) && ( + + )} + + ); +} diff --git a/android/didi-app-source/src/components/sections/ClaimsResults.tsx b/android/didi-app-source/src/components/sections/ClaimsResults.tsx new file mode 100644 index 0000000..a70537b --- /dev/null +++ b/android/didi-app-source/src/components/sections/ClaimsResults.tsx @@ -0,0 +1,219 @@ +import React, { useState } from 'react'; +import { View, Text, StyleSheet, Linking, TouchableOpacity } from 'react-native'; +import { + CheckSquare, XCircle, AlertTriangle, HelpCircle, Search, Timer, CheckCircle2, Plus, +} from 'lucide-react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { + HeadlineCard, FindingCard, SectionDivider, StatBlock, ChipsRow, Chip, CollapseButton, +} from '../editorial'; +import type { FindingAccent } from '../../theme/colors'; +import type { V3ClaimsResult, V3Claim } from '../../types/analysis'; +import { localized, useTranslation, enumLabel, smoothEnums } from '../../i18n'; + +const DEFAULT_VISIBLE = 4; + +const credAccent = (s: number) => + s >= 60 ? '#22c55e' : s >= 40 ? '#eab308' : s >= 20 ? '#f97316' : '#ef4444'; + +const credIcon = (s: number, total: number): LucideIcon => { + if (total === 0) return HelpCircle; + if (s >= 60) return CheckSquare; + if (s >= 40) return HelpCircle; + if (s >= 20) return AlertTriangle; + return XCircle; +}; + +const claimIcon = (status: string, color?: string): LucideIcon => { + if (status === 'verified_false' || color === 'red') return XCircle; + if (status === 'verified_true' || color === 'green') return CheckCircle2; + return HelpCircle; +}; + +const claimAccent = (status: string, color?: string): FindingAccent => { + if (status === 'verified_false' || color === 'red') return 'critical'; + if (status === 'verified_true' || color === 'green') return 'success'; + return 'neutral'; +}; + +const stanceFromString = (s: string): 'contradicts' | 'supports' | 'neutral' => { + const u = (s || '').toUpperCase(); + if (u === 'CONTRADICTS') return 'contradicts'; + if (u === 'SUPPORTS') return 'supports'; + return 'neutral'; +}; + +const sourceHostname = (url: string): string => + (url || '').replace(/^https?:\/\/(www\.)?/, '').split('/')[0]; + +const buildTldr = (r: V3ClaimsResult, percent: number, isRo: boolean): string => { + const total = r.total_claims || r.claims_verified?.length || 0; + if (total === 0) { + return isRo + ? 'Nicio afirmație verificabilă în acest conținut.' + : 'No verifiable claims found in this content.'; + } + const parts: string[] = []; + if (r.verified_true > 0) parts.push(`${r.verified_true} ${isRo ? 'adevărate' : 'true'}`); + if (r.verified_false > 0) parts.push(`${r.verified_false} ${isRo ? 'false' : 'false'}`); + if (r.unverified > 0) parts.push(`${r.unverified} ${isRo ? 'neverificate' : 'unverified'}`); + if (r.opinions > 0) parts.push(`${r.opinions} ${isRo ? 'opinii' : 'opinions'}`); + return isRo + ? `${total} ${total === 1 ? 'afirmație' : 'afirmații'} analizate (${parts.join(', ')}). Scor: ${percent}%.` + : `${total} ${total === 1 ? 'claim' : 'claims'} analyzed (${parts.join(', ')}). Score: ${percent}%.`; +}; + +interface Props { + result: V3ClaimsResult; +} + +export default function ClaimsResults({ result }: Props) { + const { language } = useTranslation(); + const isRo = language === 'ro'; + const [expanded, setExpanded] = useState(false); + + const rawCred = parseFloat(String(result.credibility_score)) || 0; + const credPercent = Math.round(rawCred > 1 ? rawCred : rawCred * 100); + const accent = credAccent(credPercent); + const Icon = credIcon(credPercent, result.total_claims); + + const claims = result.claims_verified ?? []; + const visible = expanded ? claims.length : Math.min(DEFAULT_VISIBLE, claims.length); + const shown = claims.slice(0, visible); + const remaining = claims.length - visible; + + const summaryParts = [ + `${result.total_claims} ${isRo ? 'total' : 'total'}`, + result.verified_true > 0 ? `${result.verified_true} ${isRo ? 'adevărate' : 'true'}` : null, + result.verified_false > 0 ? `${result.verified_false} ${isRo ? 'false' : 'false'}` : null, + result.unverified > 0 ? `${result.unverified} ${isRo ? 'neverificate' : 'unverified'}` : null, + ].filter(Boolean).join(' · '); + + return ( + + + + {result.total_claims > 0 && ( + + + {result.verified_true > 0 && ( + {result.verified_true} {isRo ? 'adevărate' : 'verified true'} + )} + {result.verified_false > 0 && ( + {result.verified_false} {isRo ? 'false' : 'verified false'} + )} + {result.unverified > 0 && ( + {result.unverified} {isRo ? 'neverificate' : 'unverified'} + )} + {result.opinions > 0 && ( + {result.opinions} {isRo ? 'opinii' : 'opinions'} + )} + {result.web_searches_made > 0 && ( + }> + {result.web_searches_made} {isRo ? 'căutări web' : 'web searches'} + + )} + {result.total_duration_ms != null && result.total_duration_ms > 0 && ( + }> + {(result.total_duration_ms / 1000).toFixed(1)}s + + )} + + + )} + + {claims.length > 0 && ( + <> + + {shown.map((claim: V3Claim, i) => { + const Icon2 = claimIcon(claim.status, claim.status_color); + const accent2 = claimAccent(claim.status, claim.status_color); + const statusName = localized(claim, 'status_name') || enumLabel(claim.status || ''); + const typeName = (claim as any).type_name || enumLabel(claim.type || ''); + const eyebrowParts = [ + statusName, + typeName, + (claim as any).priority ? `${isRo ? 'prioritate' : 'priority'} ${(claim as any).priority}` : null, + ].filter(Boolean) as string[]; + const sources = (claim.sources ?? []).slice(0, 6); + return ( + 0 ? ( + + {(() => { + const dom = stanceFromString(sources[0]?.stance); + const stanceText = isRo + ? (dom === 'contradicts' ? 'Contrazice' : dom === 'supports' ? 'Susține' : 'Neutru') + : (dom === 'contradicts' ? 'Contradicts' : dom === 'supports' ? 'Supports' : 'Neutral'); + const stanceColor = dom === 'contradicts' ? '#ef4444' : dom === 'supports' ? '#22c55e' : '#94a3b8'; + return ( + + {stanceText} + + ); + })()} + {sources.map((src, idx) => ( + + {idx > 0 && ·} + Linking.openURL(src.url)}> + {sourceHostname(src.url)} + + + ))} + + ) : undefined + } + /> + ); + })} + {remaining > 0 && ( + setExpanded(true)} + expanded={false} + leading={} + label={ + isRo + ? `Vezi celelalte ${remaining} ${remaining === 1 ? 'afirmație' : 'afirmații'}` + : `Show ${remaining} more ${remaining === 1 ? 'claim' : 'claims'}` + } + /> + )} + {expanded && claims.length > DEFAULT_VISIBLE && ( + setExpanded(false)} + expanded={true} + label={isRo ? 'Ascunde' : 'Hide'} + /> + )} + + )} + + ); +} + +const s = StyleSheet.create({ + sourceRow: { flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 6 }, + stancePill: { paddingHorizontal: 7, paddingVertical: 2, borderRadius: 4 }, + stanceText: { fontSize: 9.5, fontWeight: '700', letterSpacing: 0.5, textTransform: 'uppercase' }, + sep: { color: 'rgba(255,255,255,0.25)', fontSize: 12 }, + link: { color: '#a78bfa', fontSize: 12.5, fontWeight: '500' }, +}); diff --git a/android/didi-app-source/src/components/sections/SourceResults.tsx b/android/didi-app-source/src/components/sections/SourceResults.tsx new file mode 100644 index 0000000..ae8214e --- /dev/null +++ b/android/didi-app-source/src/components/sections/SourceResults.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { + ShieldCheck, ShieldAlert, AlertTriangle, AlertOctagon, AlertCircle, +} from 'lucide-react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { + HeadlineCard, SectionDivider, FindingCard, + ScoreLine, ScoresBlock, +} from '../editorial'; +import type { V3SourceAssessmentResult } from '../../types/analysis'; +import { useTranslation, enumLabel } from '../../i18n'; +import { ACCENT, FONT_WEIGHTS } from '../../theme/colors'; + +const trustAccent = (s: number) => + s >= 70 ? '#22c55e' : s >= 40 ? '#eab308' : s >= 20 ? '#f97316' : '#ef4444'; + +const trustIcon = (s: number): LucideIcon => { + if (s >= 70) return ShieldCheck; + if (s >= 40) return ShieldAlert; + if (s >= 20) return AlertTriangle; + return AlertOctagon; +}; + +const barColor = (score: number) => + score >= 70 ? '#22c55e' : score >= 40 ? '#f97316' : '#ef4444'; + +const buildTldr = (r: V3SourceAssessmentResult, isRo: boolean): string => { + const verdictTxt = enumLabel(r.verdict || '').toLowerCase(); + const riskTxt = enumLabel(r.risk_level || '').toLowerCase(); + if (isRo) { + return `Sursă ${verdictTxt} cu scor de încredere ${r.trust_score}/100${riskTxt ? `, risc ${riskTxt}` : ''}.`; + } + return `${verdictTxt.charAt(0).toUpperCase() + verdictTxt.slice(1)} source with trust score ${r.trust_score}/100${riskTxt ? ` · ${riskTxt} risk` : ''}.`; +}; + +interface Props { + result: V3SourceAssessmentResult; +} + +export default function SourceResults({ result: r }: Props) { + const { language } = useTranslation(); + const isRo = language === 'ro'; + const accent = trustAccent(r.trust_score); + const Icon = trustIcon(r.trust_score); + + const verdictLabel = enumLabel(r.verdict || ''); + const pub = r.publication || ({} as any); + const auth = r.author || ({} as any); + const plat = r.platform || ({} as any); + const dom = r.domain || ({} as any); + + const axes = [ + { name: isRo ? 'Publicație' : 'Publication', score: pub.score ?? 0 }, + { name: isRo ? 'Autor' : 'Author', score: auth.score ?? 0 }, + { name: isRo ? 'Platformă' : 'Platform', score: plat.score ?? 0 }, + { name: isRo ? 'Domeniu' : 'Domain', score: dom.score ?? 0 }, + ]; + + const flags: string[] = []; + if (!pub.confirmed) flags.push(isRo ? 'Publicație neverificată' : 'Publication unverified'); + if (!auth.confirmed) flags.push(isRo ? 'Autor neidentificat' : 'Author unidentified'); + if (!dom.name || dom.name === 'N/A') flags.push(isRo ? 'Fără domeniu' : 'No domain'); + (r.red_flags ?? []).forEach(f => flags.push(f.replace(/_/g, ' ').toLowerCase())); + + return ( + + + + + + {/* Single big finding card with the 4 axes inside */} + = 70 ? 'success' : r.trust_score >= 40 ? 'warning' : 'critical'} + eyebrow={verdictLabel} + headline={ + isRo + ? `${verdictLabel} — încredere ${r.trust_score}/100${r.risk_level ? ` · risc ${enumLabel(r.risk_level).toLowerCase()}` : ''}` + : `${verdictLabel} — trust ${r.trust_score}/100${r.risk_level ? ` · ${enumLabel(r.risk_level).toLowerCase()} risk` : ''}` + } + /> + + + + {axes.map((a, i) => ( + + ))} + + + {flags.length > 0 && ( + + + {flags.map((f, idx) => ( + + {idx > 0 && ·} + {f} + + ))} + + )} + + + ); +} + +const s = StyleSheet.create({ + scoresWrap: { + paddingHorizontal: 18, + paddingVertical: 14, + backgroundColor: 'rgba(255,255,255,0.018)', + borderRadius: 16, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.04)', + gap: 14, + marginTop: -4, + marginBottom: 12, + }, + flagsRow: { + flexDirection: 'row', + flexWrap: 'wrap', + alignItems: 'center', + gap: 6, + marginTop: 6, + }, + flagText: { + fontSize: 12.5, + color: 'rgba(255,255,255,0.55)', + letterSpacing: -0.05, + }, + sep: { + color: 'rgba(255,255,255,0.25)', + fontSize: 12, + }, +}); diff --git a/android/didi-app-source/src/components/sections/TechniquesResults.tsx b/android/didi-app-source/src/components/sections/TechniquesResults.tsx new file mode 100644 index 0000000..3164849 --- /dev/null +++ b/android/didi-app-source/src/components/sections/TechniquesResults.tsx @@ -0,0 +1,183 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { + AlertOctagon, AlertTriangle, Info, ShieldCheck, AlertCircle, Cpu, Layers, Target, Plus, +} from 'lucide-react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { + HeadlineCard, FindingCard, SectionDivider, StatBlock, ChipsRow, Chip, CollapseButton, +} from '../editorial'; +import type { FindingAccent } from '../../theme/colors'; +import type { V3TechniquesResult, TechniqueDefinition } from '../../types/analysis'; +import { formatTechniqueName } from '../../utils/formatTechniqueName'; +import { localized, useTranslation, smoothEnums } from '../../i18n'; + +const DEFAULT_VISIBLE = 5; + +const scoreAccent = (s: number) => + s >= 70 ? '#ef4444' : s >= 40 ? '#f97316' : s >= 20 ? '#eab308' : '#22c55e'; + +const scoreIcon = (s: number): LucideIcon => { + if (s >= 70) return AlertOctagon; + if (s >= 40) return AlertTriangle; + if (s >= 20) return Info; + return ShieldCheck; +}; + +const scoreCategory = (s: number, isRo: boolean): string => { + if (s >= 70) return isRo ? 'Critic' : 'Critical'; + if (s >= 40) return isRo ? 'Ridicat' : 'High'; + if (s >= 20) return isRo ? 'Mediu' : 'Medium'; + return isRo ? 'Scăzut' : 'Low'; +}; + +const techIcon = (name: string): LucideIcon => { + const dim = (name?.split('.')[0] || '').toUpperCase(); + if (dim === 'D3') return Cpu; + if (dim === 'D5') return Layers; + if (dim === 'D8') return Target; + return AlertCircle; +}; + +const techAccent = (severity: number): FindingAccent => { + if (severity >= 70) return 'critical'; + if (severity >= 40) return 'warning'; + return 'info'; +}; + +const buildTldr = (r: V3TechniquesResult, isRo: boolean): string => { + const techCount = r.techniques_detected?.length ?? 0; + const dimCount = r.dimensions_affected?.length ?? 0; + const dims = (r.dimensions_affected ?? []).join(' · '); + if (techCount === 0) { + return isRo + ? 'Nicio tehnică de manipulare detectată în acest conținut.' + : 'No manipulation techniques detected in this content.'; + } + return isRo + ? `${techCount} ${techCount === 1 ? 'tehnică' : 'tehnici'} de manipulare detectate în ${dimCount} ${dimCount === 1 ? 'dimensiune' : 'dimensiuni'}${dims ? ` (${dims})` : ''}.` + : `${techCount} manipulation ${techCount === 1 ? 'technique' : 'techniques'} detected across ${dimCount} ${dimCount === 1 ? 'dimension' : 'dimensions'}${dims ? ` (${dims})` : ''}.`; +}; + +interface Props { + result: V3TechniquesResult; + defMap?: Map; +} + +export default function TechniquesResults({ result, defMap }: Props) { + const { language } = useTranslation(); + const isRo = language === 'ro'; + const [expanded, setExpanded] = useState(false); + + const score = result.manipulation_score ?? 0; + const accent = scoreAccent(score); + const Icon = scoreIcon(score); + const techCount = result.techniques_detected?.length ?? 0; + const dimCount = result.dimensions_affected?.length ?? 0; + const techs = result.techniques_detected ?? []; + const visible = expanded ? techs.length : Math.min(DEFAULT_VISIBLE, techs.length); + const shown = techs.slice(0, visible); + const remaining = techs.length - visible; + const warningFlags: string[] = (result.coupling_context as any)?.for_claims?.warning_flags ?? []; + + return ( + + 0 + ? `${techCount} ${isRo ? (techCount === 1 ? 'tehnică' : 'tehnici') : (techCount === 1 ? 'technique' : 'techniques')} · ${dimCount} ${isRo ? (dimCount === 1 ? 'dimensiune' : 'dimensiuni') : (dimCount === 1 ? 'dimension' : 'dimensions')}` + : `${techCount} ${isRo ? (techCount === 1 ? 'tehnică' : 'tehnici') : (techCount === 1 ? 'technique' : 'techniques')}` + } + tldr={buildTldr(result, isRo)} + /> + + {(score > 0 || dimCount > 0) && ( + + + {(result.dimensions_affected ?? []).map(dim => ( + {dim} + ))} + {techCount > 0 && ( + {techCount} {isRo ? 'tehnici detectate' : 'techniques detected'} + )} + + + )} + + {techCount > 0 && ( + <> + 0 ? ` · ${(result.dimensions_affected ?? []).join(' · ')}` : ''}`} + /> + {shown.map((tech, i) => { + const sev = tech.severity ?? (tech.intensity * 33); + const rawName = localized(tech, 'name') || `Technique #${tech.technique_id}`; + const def = defMap?.get(rawName); + const dimName = localized(tech, 'dimension_name', 'dimension'); + const subdimName = localized(tech, 'subdimension_name', 'subdimension'); + const eyebrowParts = [ + (tech.name?.split('.')[0] || '').toUpperCase() || undefined, + dimName ? (subdimName ? `${dimName} · ${subdimName}` : dimName) : undefined, + tech.severity != null ? `${isRo ? 'severitate' : 'severity'} ${tech.severity}` : undefined, + ].filter(Boolean) as string[]; + return ( + + ); + })} + {remaining > 0 && ( + setExpanded(true)} + expanded={false} + leading={} + label={ + isRo + ? `Vezi celelalte ${remaining} ${remaining === 1 ? 'tehnică' : 'tehnici'}` + : `Show ${remaining} more ${remaining === 1 ? 'technique' : 'techniques'}` + } + /> + )} + {expanded && techs.length > DEFAULT_VISIBLE && ( + setExpanded(false)} + expanded={true} + label={isRo ? 'Ascunde' : 'Hide'} + /> + )} + + )} + + {techCount === 0 && ( + + )} + + {warningFlags.length > 0 && ( + f.replace(/_/g, ' ')).join(' · ')} + /> + )} + + ); +} diff --git a/android/didi-app-source/src/components/sections/Verdict.tsx b/android/didi-app-source/src/components/sections/Verdict.tsx new file mode 100644 index 0000000..724d28d --- /dev/null +++ b/android/didi-app-source/src/components/sections/Verdict.tsx @@ -0,0 +1,384 @@ +import React, { useState } from 'react'; +import { View, Text, StyleSheet, Linking, TouchableOpacity } from 'react-native'; +import { + AlertOctagon, AlertTriangle, Info, ShieldCheck, OctagonX, BarChart3, Layers, + AlertCircle, Cpu, ExternalLink, ChevronDown, Quote, Users, Zap, UserX, ShieldQuestion, TrendingUp, Timer, XCircle, Tag, +} from 'lucide-react-native'; +import type { LucideIcon } from 'lucide-react-native'; +import { + HeadlineCard, FindingCard, SectionDivider, ActionCallout, StatBlock, ChipsRow, Chip, + CollapseButton, ScoreLine, ScoresBlock, +} from '../editorial'; +import type { FindingAccent } from '../../theme/colors'; +import { COLORS, ACCENT, getRiskColor as getRiskColorByName } from '../../theme/colors'; +import type { V3Verdict, AnalysisSession } from '../../types/analysis'; +import { useTranslation, enumLabel, localized, smoothEnums } from '../../i18n'; +import TechniquesResults from './TechniquesResults'; +import AiResults, { toAiDisplayResult } from './AiResults'; +import ClaimsResults from './ClaimsResults'; +import SourceResults from './SourceResults'; +import type { TechniqueDefinition } from '../../types/analysis'; + +const SEVERITY_RANK: Record = { critical: 0, warning: 1, info: 2 }; + +/** Hide debug strings the backend leaks into evidence_ref.quote + * (e.g. `ai_probability=85/100, verdict="LIKELY_AI"`, `Claims: [SKIPPED]`, `data_quality=full`). */ +function cleanQuote(q: string | null | undefined): string | undefined { + if (!q) return undefined; + if (/\[SKIPPED\]/i.test(q)) return undefined; + if (/^\s*\w+\s*=\s*[\d"']/.test(q)) return undefined; + if (/data_quality\s*=/i.test(q)) return undefined; + return q; +} + +interface KeyFinding { + type: string; + severity: string; + ro: string; + en: string; + evidence_ref?: { quote?: string; source_url?: string }; +} + +const TYPE_ICONS: Record = { + false_claim: BarChart3, + fabricated_quote: Quote, + manipulation: AlertCircle, + urgency: Zap, + imposter_content: UserX, + conspiracy: ShieldQuestion, + demographic: Users, +}; + +function findingIconAccent(f: KeyFinding): { icon: LucideIcon; accent: FindingAccent } { + const accent: FindingAccent = + f.severity === 'critical' ? 'critical' + : f.severity === 'warning' ? 'warning' + : 'info'; + return { icon: TYPE_ICONS[f.type] || AlertCircle, accent }; +} + +function categoryIcon(color: string): LucideIcon { + if (color === 'red' || color === 'darkred') return AlertOctagon; + if (color === 'orange') return AlertTriangle; + if (color === 'yellow') return Info; + if (color === 'green' || color === 'lightgreen') return ShieldCheck; + return Info; +} + +function actionEyebrow(severity: string, isRo: boolean): string { + if (severity === 'critical') return isRo ? 'NU DISTRIBUI' : 'DO NOT SHARE'; + if (severity === 'warning') return isRo ? 'CITEȘTE CRITIC' : 'READ CRITICALLY'; + return isRo ? 'DE REȚINUT' : 'NOTE'; +} + +function safeHostname(url: string): string { + try { + return new URL(url).hostname; + } catch { + return url; + } +} + +interface Props { + verdict: V3Verdict; + fullResult: Partial | null; + techniqueDefMap?: Map; + totalDuration?: number | null; +} + +export default function Verdict({ verdict, fullResult, techniqueDefMap, totalDuration }: Props) { + const { language } = useTranslation(); + const isRo = language === 'ro'; + const [secondaryOpen, setSecondaryOpen] = useState(false); + const [detailsOpen, setDetailsOpen] = useState(false); + + const summary = (verdict.context_summary as any)?.verdict_summary; + const accentName = verdict.risk_category_color || 'red'; + const accent = accentName.startsWith('#') ? accentName : getRiskColorByName(accentName); + + const sortedFindings: KeyFinding[] = React.useMemo(() => { + if (!summary?.key_findings) return []; + return [...summary.key_findings].sort( + (a: KeyFinding, b: KeyFinding) => (SEVERITY_RANK[a.severity] ?? 3) - (SEVERITY_RANK[b.severity] ?? 3), + ); + }, [summary]); + + const dominant = sortedFindings[0]; + const rest = sortedFindings.slice(1); + + const CatIcon = categoryIcon(verdict.risk_category_color || ''); + + const techniquesCount = (verdict.context_summary as any)?.techniques_detected ?? 0; + const falseClaims = (verdict.context_summary as any)?.claims_false ?? 0; + const elapsedSec = totalDuration ? `${(totalDuration / 1000).toFixed(1)}s` : null; + const viralityScore = verdict.virality_score; + const viralityLevel = verdict.virality_level; + + const scoreEntries = [ + { name: isRo ? 'Manipulare' : 'Manipulation', value: verdict.score_manipulation ?? null }, + { name: 'Claims', value: verdict.score_claims ?? null }, + { name: isRo ? 'AI generation' : 'AI generation', value: verdict.score_ai ?? null }, + { name: isRo ? 'Sursă' : 'Source', value: verdict.score_source ?? null }, + ]; + + return ( + + {summary ? ( + <> + {/* TIER 1 — HEADLINE */} + + + {/* TIER 2 — ACTION CALLOUT */} + {(summary.what_to_do_ro || summary.what_to_do_en) && ( + + )} + + {/* TIER 3 — DOMINANT FINDING */} + {dominant && (() => { + const { icon, accent: a } = findingIconAccent(dominant); + return ( + <> + + + + {isRo ? 'Verificat de' : 'Verified by'} + Linking.openURL(dominant.evidence_ref!.source_url!)}> + {safeHostname(dominant.evidence_ref!.source_url!)} + + + ) : undefined + } + /> + {rest.length > 0 && ( + <> + setSecondaryOpen(o => !o)} + expanded={secondaryOpen} + leading={} + label={ + secondaryOpen + ? (isRo ? 'Ascunde celelalte semne' : 'Hide other signals') + : (isRo + ? `Vezi ${rest.length} ${rest.length === 1 ? 'alt semn' : 'alte semne'}` + : `Show ${rest.length} more ${rest.length === 1 ? 'signal' : 'signals'}`) + } + /> + {secondaryOpen && ( + <> + + {rest.map((f, idx) => { + const { icon: ic, accent: ac } = findingIconAccent(f); + return ( + + + Linking.openURL(f.evidence_ref!.source_url!)}> + {safeHostname(f.evidence_ref!.source_url!)} + + + ) : ( + + + {f.type.replace(/_/g, ' ')} + + ) + } + /> + ); + })} + + )} + + )} + + ); + })()} + + {/* TIER 4 — TECHNICAL DETAILS */} + setDetailsOpen(o => !o)} + expanded={detailsOpen} + leading={} + label={isRo ? 'Detalii tehnice' : 'Technical details'} + /> + + {detailsOpen && ( + <> + + + {scoreEntries.map((entry, i) => ( + entry.value != null ? ( + = 70 ? '#ef4444' : entry.value >= 40 ? '#f97316' : '#22c55e'} + delay={200 + i * 80} + /> + ) : ( + + {entry.name} + + + + ) + ))} + + + {viralityScore != null && ( + = 50 ? 'critical' : viralityScore >= 25 ? 'warning' : 'success'} + leading={= 50 ? ACCENT.critical : viralityScore >= 25 ? ACCENT.warning : ACCENT.success} strokeWidth={2} />} + > + Virality {viralityScore}{viralityLevel ? ` ${enumLabel(viralityLevel)}` : ''} + + )} + {techniquesCount > 0 && ( + }> + {techniquesCount} {isRo ? 'tehnici' : 'techniques'} + + )} + {falseClaims > 0 && ( + }> + {falseClaims} {isRo ? 'false' : 'false claims'} + + )} + {elapsedSec && ( + }> + {elapsedSec} + + )} + + + + {fullResult?.ai_tampered && ( + + )} + {fullResult?.source_assessment && ( + + )} + {fullResult?.techniques && ( + + )} + {fullResult?.claims && } + + )} + + ) : ( + // ── LEGACY FALLBACK ── + <> + + {(verdict.explanation_en || verdict.explanation_ro) && ( + + {smoothEnums(localized(verdict, 'explanation'))} + + )} + + )} + + ); +} + +const s = StyleSheet.create({ + sourceRow: { + flexDirection: 'row', + alignItems: 'center', + flexWrap: 'wrap', + gap: 6, + }, + sourceText: { + color: 'rgba(255,255,255,0.55)', + fontSize: 12.5, + }, + link: { + color: '#a78bfa', + fontSize: 12.5, + fontWeight: '500', + }, + skippedRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + skippedName: { + fontSize: 13, + color: 'rgba(255,255,255,0.7)', + width: 100, + }, + skippedBar: { + flex: 1, + height: 4, + }, + skippedDash: { + fontSize: 13, + color: 'rgba(255,255,255,0.28)', + width: 40, + textAlign: 'right', + }, + legacyExpl: { + paddingHorizontal: 18, + paddingVertical: 14, + backgroundColor: 'rgba(255,255,255,0.025)', + borderRadius: 16, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.06)', + }, + legacyText: { + fontFamily: 'Merriweather-Italic', + fontSize: 14, + lineHeight: 22, + color: 'rgba(255,255,255,0.7)', + }, +}); diff --git a/android/didi-app-source/src/components/sections/index.ts b/android/didi-app-source/src/components/sections/index.ts new file mode 100644 index 0000000..034f812 --- /dev/null +++ b/android/didi-app-source/src/components/sections/index.ts @@ -0,0 +1,6 @@ +export { default as TechniquesResults } from './TechniquesResults'; +export { default as AiResults, toAiDisplayResult } from './AiResults'; +export type { AiDisplayResult } from './AiResults'; +export { default as ClaimsResults } from './ClaimsResults'; +export { default as SourceResults } from './SourceResults'; +export { default as Verdict } from './Verdict'; diff --git a/android/didi-app-source/src/components/settings/ChangePasswordSection.tsx b/android/didi-app-source/src/components/settings/ChangePasswordSection.tsx new file mode 100644 index 0000000..9e988c7 --- /dev/null +++ b/android/didi-app-source/src/components/settings/ChangePasswordSection.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../../theme/colors'; +import type { RootStackParamList } from '../../navigation/AppNavigator'; +import { useTranslation } from '../../i18n'; + +export default function ChangePasswordSection() { + const { t } = useTranslation(); + const navigation = useNavigation>(); + + return ( + + + + P + + + {t('changePassword')} + {t('updatePassword')} + + + + navigation.navigate('ChangePassword')} + accessibilityRole="button" + accessibilityLabel={t('changePassword')} + > + + {t('changePassword')} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginBottom: SPACING.md, + }, + headerRow: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.lg, + }, + iconCircle: { + width: LAYOUT.ICON_CIRCLE_SIZE, + height: LAYOUT.ICON_CIRCLE_SIZE, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.purple10, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + iconText: { + color: COLORS.brand.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + textContainer: { + flex: 1, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + subtitle: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + marginTop: 2, + }, + button: { + borderRadius: RADIUS.xl, + paddingVertical: SPACING.sm + 4, + alignItems: 'center', + justifyContent: 'center', + }, + buttonText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/components/settings/LanguageSection.tsx b/android/didi-app-source/src/components/settings/LanguageSection.tsx new file mode 100644 index 0000000..10d0486 --- /dev/null +++ b/android/didi-app-source/src/components/settings/LanguageSection.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { COLORS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS } from '../../theme/colors'; +import { useLanguageStore, useTranslation } from '../../i18n'; +import type { Language } from '../../i18n'; + +const LANGUAGES: { key: Language; labelKey: 'english' | 'romanian'; flag: string }[] = [ + { key: 'en', labelKey: 'english', flag: '🇬🇧' }, + { key: 'ro', labelKey: 'romanian', flag: '🇷🇴' }, +]; + +export default function LanguageSection() { + const { t } = useTranslation(); + const { language, setLanguage } = useLanguageStore(); + + return ( + + {t('language')} + {t('languageSubtitle')} + + + {LANGUAGES.map((lang) => { + const selected = language === lang.key; + return ( + setLanguage(lang.key)} + activeOpacity={0.7} + accessibilityRole="button" + accessibilityLabel={t(lang.labelKey)} + accessibilityState={{ selected }} + > + {lang.flag} + + {t(lang.labelKey)} + + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: SPACING.lg, + marginBottom: SPACING.md, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.xs, + }, + subtitle: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginBottom: SPACING.md, + }, + optionsRow: { + flexDirection: 'row', + gap: SPACING.md, + }, + option: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: SPACING.sm, + backgroundColor: COLORS.glass.white5, + borderWidth: 1, + borderColor: COLORS.border.secondary, + borderRadius: RADIUS.lg, + paddingVertical: SPACING.md, + }, + optionSelected: { + borderColor: COLORS.brand.primary, + backgroundColor: COLORS.glass.purple10, + }, + flag: { + fontSize: FONT_SIZES.xl, + color: COLORS.text.primary, + }, + optionText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + optionTextSelected: { + color: COLORS.brand.primaryLight, + }, +}); diff --git a/android/didi-app-source/src/components/settings/ProfileSection.tsx b/android/didi-app-source/src/components/settings/ProfileSection.tsx new file mode 100644 index 0000000..776a4ba --- /dev/null +++ b/android/didi-app-source/src/components/settings/ProfileSection.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../../theme/colors'; +import type { UserProfile } from '../../types/auth'; +import { useTranslation } from '../../i18n'; + +interface ProfileSectionProps { + profile: UserProfile | null; +} + +export default function ProfileSection({ profile }: ProfileSectionProps) { + const { t } = useTranslation(); + const initials = profile + ? `${profile.firstName?.[0] || ''}${profile.lastName?.[0] || ''}`.toUpperCase() + : '?'; + + return ( + + {t('profile')} + + + + {initials} + + + + {profile ? `${profile.firstName} ${profile.lastName}` : t('loading')} + + {profile?.email || ''} + + + + + + {t('username')} + {profile?.email || '—'} + + + {t('firstName')} + {profile?.firstName || '—'} + + + {t('lastName')} + {profile?.lastName || '—'} + + + {t('email')} + {profile?.email || '—'} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginBottom: SPACING.md, + }, + sectionTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.md, + }, + avatarRow: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.lg, + }, + avatar: { + width: LAYOUT.AVATAR_SIZE_LARGE, + height: LAYOUT.AVATAR_SIZE_LARGE, + borderRadius: RADIUS.full, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + avatarText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + avatarInfo: { + flex: 1, + }, + name: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + email: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + fieldsContainer: { + gap: SPACING.sm, + }, + fieldRow: { + backgroundColor: COLORS.glass.white5, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.sm + 4, + }, + fieldLabel: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + marginBottom: 2, + }, + fieldValue: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/components/settings/SignOutSection.tsx b/android/didi-app-source/src/components/settings/SignOutSection.tsx new file mode 100644 index 0000000..9c1be2b --- /dev/null +++ b/android/didi-app-source/src/components/settings/SignOutSection.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { + COLORS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, + getBadgeBgColor, +} from '../../theme/colors'; +import { useSignOut } from '../../hooks/useSignOut'; +import { useTranslation } from '../../i18n'; + +export default function SignOutSection() { + const { t } = useTranslation(); + const handleSignOut = useSignOut(); + + return ( + + + + + + + {t('signOut')} + {t('signOutSubtitle')} + + + + + + {t('signOut')} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: getBadgeBgColor(COLORS.status.red, 0.35), + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginBottom: SPACING.md, + }, + headerRow: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.lg, + }, + iconCircle: { + width: LAYOUT.ICON_CIRCLE_SIZE, + height: LAYOUT.ICON_CIRCLE_SIZE, + borderRadius: RADIUS.full, + backgroundColor: getBadgeBgColor(COLORS.status.red, 0.12), + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + iconText: { + color: COLORS.status.red, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + textContainer: { + flex: 1, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + subtitle: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + marginTop: 2, + }, + button: { + borderRadius: RADIUS.xl, + paddingVertical: SPACING.sm + 4, + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: getBadgeBgColor(COLORS.status.red, 0.5), + backgroundColor: getBadgeBgColor(COLORS.status.red, 0.12), + }, + buttonText: { + color: COLORS.status.red, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/components/settings/SocialShareSection.tsx b/android/didi-app-source/src/components/settings/SocialShareSection.tsx new file mode 100644 index 0000000..5e82801 --- /dev/null +++ b/android/didi-app-source/src/components/settings/SocialShareSection.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { View, Text, Switch, StyleSheet } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { + COLORS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../../theme/colors'; +import type { SocialToggles } from '../../utils/socialShareStorage'; +import { useTranslation } from '../../i18n'; + +interface SocialShareSectionProps { + toggles: SocialToggles; + onToggle: (platform: keyof SocialToggles) => void; +} + +const SOCIAL_PLATFORMS: { + key: keyof SocialToggles; + name: string; + description: string; + icon: string; + colors: [string, string]; +}[] = [ + { + key: 'facebook', + name: 'Facebook', + description: 'Auto-analyze shared Facebook links', + icon: 'f', + colors: [COLORS.social.facebookStart, COLORS.social.facebookEnd], + }, + { + key: 'x', + name: 'X (Twitter)', + description: 'Auto-analyze shared X/Twitter links', + icon: 'X', + colors: [COLORS.social.twitterStart, COLORS.social.twitterEnd], + }, + { + key: 'tiktok', + name: 'TikTok', + description: 'Auto-analyze shared TikTok links', + icon: 'T', + colors: [COLORS.social.tiktokStart, COLORS.social.tiktokEnd], + }, +]; + +export default function SocialShareSection({ toggles, onToggle }: SocialShareSectionProps) { + const { t } = useTranslation(); + + const platformLabels: Record = { + facebook: { name: t('facebook'), description: t('autoAnalyzeFacebook') }, + x: { name: t('xTwitter'), description: t('autoAnalyzeX') }, + tiktok: { name: t('tiktok'), description: t('autoAnalyzeTiktok') }, + }; + + return ( + + {t('socialShare')} + + {t('socialShareDesc')} + + + + {SOCIAL_PLATFORMS.map((platform) => ( + + + {platform.icon} + + + + {platformLabels[platform.key].name} + {platformLabels[platform.key].description} + + + onToggle(platform.key)} + accessibilityRole="switch" + accessibilityLabel={platformLabels[platform.key].description} + accessibilityState={{ checked: toggles[platform.key] }} + trackColor={{ + false: COLORS.glass.white10, + true: COLORS.glass.purple35, + }} + thumbColor={toggles[platform.key] ? COLORS.brand.primary : COLORS.text.hint} + /> + + ))} + + + + + {t('socialShareNote')} + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginBottom: SPACING.md, + }, + sectionTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.xs, + }, + sectionSubtitle: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + lineHeight: 18, + marginBottom: SPACING.lg, + }, + togglesList: { + gap: SPACING.md, + }, + toggleRow: { + flexDirection: 'row', + alignItems: 'center', + }, + iconCircle: { + width: 40, + height: 40, + borderRadius: RADIUS.full, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + iconText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + }, + toggleTextContainer: { + flex: 1, + marginRight: SPACING.sm, + }, + toggleName: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + toggleDescription: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + marginTop: 1, + }, + noteContainer: { + backgroundColor: COLORS.glass.white5, + borderRadius: RADIUS.lg, + padding: SPACING.md, + marginTop: SPACING.lg, + }, + noteText: { + color: COLORS.text.muted, + fontSize: FONT_SIZES.xs, + lineHeight: 18, + }, +}); diff --git a/android/didi-app-source/src/components/settings/SubscriptionSection.tsx b/android/didi-app-source/src/components/settings/SubscriptionSection.tsx new file mode 100644 index 0000000..1f7417c --- /dev/null +++ b/android/didi-app-source/src/components/settings/SubscriptionSection.tsx @@ -0,0 +1,247 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { + COLORS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../../theme/colors'; +import type { UserProfile } from '../../types/auth'; +import { useTranslation } from '../../i18n'; + +interface SubscriptionSectionProps { + profile: UserProfile | null; + usageStats: any; + creditsData: { credits_remained: number; credits_spent: number } | null; +} + +export default function SubscriptionSection({ profile, usageStats, creditsData }: SubscriptionSectionProps) { + const { t } = useTranslation(); + const plan = usageStats?.plan as any; + const sub = usageStats?.subscription as any; + + const planName = plan?.name || 'Free'; + const isActive = sub?.isActive ?? sub?.is_active ?? false; + const price = plan?.priceAmount ?? plan?.price_amount ?? 0; + + // Credits from auth/credits endpoint (authoritative source) + const creditsRemaining = creditsData?.credits_remained ?? profile?.creditsRemained ?? 0; + const creditsSpent = creditsData?.credits_spent ?? profile?.creditsSpent ?? 0; + const creditsTotal = creditsRemaining + creditsSpent; + const creditPercent = creditsTotal > 0 ? (creditsSpent / creditsTotal) * 100 : 0; + + const creditsPerCycle = plan?.creditsPerCycle ?? plan?.credits_per_cycle ?? creditsTotal; + const maxImages = plan?.maxImages ?? plan?.max_images ?? null; + const maxVideoMinutes = plan?.maxVideoMinutes ?? plan?.max_video_minutes ?? null; + const storageLimitGb = plan?.storageLimitGb ?? plan?.storage_limit_gb ?? null; + + const creditBarColor = + creditPercent > 90 ? COLORS.status.red : + creditPercent > 75 ? COLORS.status.yellow : + COLORS.brand.accentCyan; + + return ( + + {t('subscriptionCredits')} + + {/* Plan info row */} + + + {planName} + + + {isActive ? t('active') : t('inactive')} + ${price}/mo + + + {/* Credits */} + + + {t('credits')} + + {creditsRemaining} / {creditsTotal} + + + + + + + + + + {t('remaining')} + {creditsRemaining} + + + {t('total')} + {creditsTotal} + + + {t('spent')} + {creditsSpent} + + + + + {/* Plan limits */} + + {t('planLimits')} + + + {t('creditsCycle')} + {creditsPerCycle || '—'} + + + {t('maxImages')} + {maxImages ?? '—'} + + + {t('videoMinutes')} + {maxVideoMinutes ?? '—'} + + + {t('storage')} + {storageLimitGb ?? '—'} GB + + + + + ); +} + +const styles = StyleSheet.create({ + card: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginBottom: SPACING.md, + }, + sectionTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.md, + }, + planRow: { + flexDirection: 'row', + alignItems: 'center', + gap: SPACING.sm, + marginBottom: SPACING.lg, + }, + planBadge: { + backgroundColor: COLORS.glass.cyan10, + borderWidth: 1, + borderColor: COLORS.glass.cyan20, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.sm + 4, + paddingVertical: SPACING.xs, + }, + planBadgeText: { + color: COLORS.brand.accentCyan, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, + statusDot: { + width: 8, + height: 8, + borderRadius: RADIUS.full, + }, + statusText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + }, + price: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.sm, + marginLeft: 'auto', + }, + creditsContainer: { + marginBottom: SPACING.lg, + }, + creditsHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: SPACING.sm, + }, + creditsLabel: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + creditsValue: { + color: COLORS.brand.accentCyan, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + progressBg: { + height: 8, + backgroundColor: COLORS.glass.white10, + borderRadius: RADIUS.sm, + overflow: 'hidden', + marginBottom: SPACING.sm, + }, + progressFill: { + height: '100%', + borderRadius: RADIUS.sm, + }, + creditsDetails: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + detailItem: { + alignItems: 'center', + }, + detailLabel: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + }, + detailValue: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + marginTop: 2, + }, + limitsContainer: { + backgroundColor: COLORS.glass.white5, + borderRadius: RADIUS.lg, + padding: SPACING.md, + }, + limitsTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.sm, + }, + limitsGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: SPACING.sm, + }, + limitItem: { + width: '47%', + flexDirection: 'row', + justifyContent: 'space-between', + }, + limitLabel: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + }, + limitValue: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/hooks/useShareIntent.ts b/android/didi-app-source/src/hooks/useShareIntent.ts new file mode 100644 index 0000000..4855d33 --- /dev/null +++ b/android/didi-app-source/src/hooks/useShareIntent.ts @@ -0,0 +1,24 @@ +import { useShareIntentContext } from 'expo-share-intent'; +import { resolveShareIntent, type SharedPayload } from '../utils/shareIntent'; + +/** + * Returns the normalized shared content (URL, plain text, or image) + * received via the OS share sheet, or null when nothing was shared. + */ +export function useSharedContent() { + const { hasShareIntent, shareIntent, resetShareIntent, isReady } = + useShareIntentContext(); + + let sharedPayload: SharedPayload | null = null; + + if (hasShareIntent && shareIntent) { + sharedPayload = resolveShareIntent(shareIntent); + } + + return { + isReady, + hasShareIntent, + sharedPayload, + resetShareIntent, + }; +} diff --git a/android/didi-app-source/src/hooks/useSignOut.ts b/android/didi-app-source/src/hooks/useSignOut.ts new file mode 100644 index 0000000..acddf6c --- /dev/null +++ b/android/didi-app-source/src/hooks/useSignOut.ts @@ -0,0 +1,41 @@ +import { useCallback } from 'react'; +import { Alert, Platform } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useAuthStore } from '../store/authStore'; +import { useTranslation } from '../i18n'; +import type { RootStackParamList } from '../navigation/AppNavigator'; + +/** + * Shared sign-out flow: confirmation prompt, server-side token revocation + + * local token clearing (clearTokens), then navigation reset to Main. + * Used by DashboardScreen and SettingsScreen. + */ +export function useSignOut() { + const navigation = useNavigation>(); + const { clearTokens } = useAuthStore(); + const { t } = useTranslation(); + + return useCallback(() => { + const doSignOut = async () => { + await clearTokens(); + navigation.reset({ index: 0, routes: [{ name: 'Main' }] }); + }; + + if (Platform.OS === 'web') { + if (window.confirm(t('signOutConfirm'))) { + doSignOut(); + } + return; + } + + Alert.alert(t('signOut'), t('signOutConfirm'), [ + { text: t('cancel'), style: 'cancel' }, + { + text: t('signOut'), + style: 'destructive', + onPress: doSignOut, + }, + ]); + }, [clearTokens, navigation, t]); +} diff --git a/android/didi-app-source/src/hooks/useTechniqueDefinitions.ts b/android/didi-app-source/src/hooks/useTechniqueDefinitions.ts new file mode 100644 index 0000000..4304791 --- /dev/null +++ b/android/didi-app-source/src/hooks/useTechniqueDefinitions.ts @@ -0,0 +1,25 @@ +import { useState, useEffect } from 'react'; +import type { TechniqueDefinition } from '../types/analysis'; +import { getTechniqueDefinitions } from '../api/techniqueDefinitions'; + +/** Fetch technique definitions (cached 5 min). Returns map by technique_name and full array. */ +export function useTechniqueDefinitions() { + const [definitions, setDefinitions] = useState([]); + const [defMap, setDefMap] = useState>(new Map()); + + useEffect(() => { + let cancelled = false; + getTechniqueDefinitions().then((defs) => { + if (cancelled) return; + setDefinitions(defs); + const map = new Map(); + for (const d of defs) { + map.set(d.technique_name, d); + } + setDefMap(map); + }); + return () => { cancelled = true; }; + }, []); + + return { definitions, defMap }; +} diff --git a/android/didi-app-source/src/i18n/en.ts b/android/didi-app-source/src/i18n/en.ts new file mode 100644 index 0000000..3082879 --- /dev/null +++ b/android/didi-app-source/src/i18n/en.ts @@ -0,0 +1,343 @@ +const en = { + // Common + back: 'Back', + cancel: 'Cancel', + delete: 'Delete', + error: 'Error', + loading: 'Loading...', + goBack: 'Go Back', + settings: 'Settings', + signOut: 'Sign Out', + signIn: 'Sign In', + createAccount: 'Create Account', + analyze: 'Analyze', + confirm: 'Confirm', + + // Main / Auth + appName: 'didi', + appSubtitle: 'Content Analysis Tool', + authenticating: 'Authenticating...', + redirectingToLogin: 'Redirecting to login...', + authError: 'Authentication Error', + authDenied: 'Authorization was denied. Please try again.', + securityError: 'Security Error', + invalidAuthResponse: 'Invalid authentication response. Please try again.', + noAuthCode: 'No authorization code received', + authFailed: 'Authentication Failed', + authFailedMessage: 'Could not complete login. Please try again.', + changePassword: 'Change Password', + redirecting: 'Redirecting...', + + // Dashboard + analysis: 'Analysis', + fullPipelineAnalysis: 'Full pipeline analysis', + skills: 'Skills', + individualComponents: 'Individual analysis components', + history: 'History', + pastAnalyses: 'Past analyses', + accountPreferences: 'Account & preferences', + signOutConfirm: 'Are you sure you want to sign out?', + signOutSubtitle: 'Sign out of your account on this device', + + // Pipeline + fullAnalysis: 'Full Analysis', + runAllComponents: 'Run all components at once', + textToAnalyze: 'Text to Analyze', + enterTextHint: 'Enter text, article, or paste content', + pasteTextPlaceholder: 'Paste text content here...', + characters: 'characters', + urlToAnalyze: 'URL to Analyze', + enterUrlHint: 'Enter a webpage URL', + urlPlaceholder: 'https://example.com/article', + imageToAnalyze: 'Image to Analyze', + takePhotoOrSelect: 'Take a photo or select from gallery', + gallery: 'Gallery', + camera: 'Camera', + audioToAnalyze: 'Audio to Analyze', + selectAudioHint: 'Select an audio file for analysis', + selectAudio: 'Select Audio', + videoToAnalyze: 'Video to Analyze', + selectVideoHint: 'Select a video file for analysis', + selectVideo: 'Select Video', + runFullAnalysis: 'Run Full Analysis', + submitting: 'Submitting...', + uploadingMedia: 'Uploading media...', + startingAnalysis: 'Starting analysis...', + invalidFile: 'Invalid File', + permissionRequired: 'Permission required', + cameraPermission: 'Camera access is needed to take photos.', + analysisError: 'Analysis Error', + failedToStartAnalysis: 'Failed to start analysis.', + contentUnavailable: 'Content Unavailable', + contentNotExtracted: 'Content could not be extracted from this URL.', + uploadManually: 'Upload Manually', + + // Tabs + tabText: 'Text', + tabImage: 'Image', + tabAudio: 'Audio', + tabVideo: 'Video', + tabUrl: 'URL', + + // Pipeline Results + analysisResults: 'Analysis Results', + processingMedia: 'Processing media...', + generatingVerdict: 'Generating verdict...', + processingProgress: 'Processing ({done}/{total})...', + percentComplete: '{percent}% complete', + analyzing: 'Analyzing...', + noResults: 'No results available', + analysisTimedOut: 'Analysis timed out. Please try again.', + connectionLost: 'Connection lost. Please check your network and try again.', + serverError: 'Analysis server error. Please try again later.', + sessionNotFound: 'Analysis session not found.', + + // Component Labels + manipulationTechniques: 'Manipulation Techniques', + claimsVerification: 'Claims Verification', + aiDetection: 'AI Detection', + sourceAssessment: 'Source Assessment', + contextAnalysis: 'Context Analysis', + + // Skills Screen + analyzeIndividualAspects: 'Analyze individual aspects', + detectManipulation: 'Detect persuasion and manipulation tactics', + evaluateSource: 'Evaluate source credibility and trustworthiness', + aiTamperDetection: 'AI Tamper Detection', + detectAiContent: 'Detect AI-generated or manipulated content', + claimsAnalysis: 'Claims Analysis', + verifyClaims: 'Verify claims and check credibility', + enterTextToAnalyze: 'Enter text content to analyze', + + // History + noAnalysesYet: 'No analyses yet', + runFirstAnalysis: 'Run your first analysis to see it here', + justNow: 'Just now', + minutesAgo: '{min}m ago', + hoursAgo: '{hr}h ago', + daysAgo: '{day}d ago', + mediaAnalysis: '(media analysis)', + deleteAnalysis: 'Delete Analysis', + deleteConfirm: 'Are you sure you want to delete this analysis?', + failedToLoadHistory: 'Failed to load history', + failedToLoadDetails: 'Failed to load details', + failedToDelete: 'Failed to delete', + + // Verdict Card + verdict: 'Verdict', + riskScore: 'Risk Score', + confidence: 'Confidence', + recommendedAction: 'Recommended Action', + + // Component Scores + componentScores: 'Component Scores', + scoreManipulation: 'Manipulation', + scoreClaims: 'Claims', + scoreAiGenerated: 'AI Generated', + scoreSource: 'Source', + scoreContext: 'Context', + + // Claims Card + credibilityScore: 'Credibility Score', + total: 'Total', + trueLabel: 'True', + falseLabel: 'False', + unverified: 'Unverified', + sources: 'Sources', + stance: 'Stance', + + // Claim Status Labels + claimVerifiedTrue: 'Verified True', + claimVerifiedFalse: 'Verified False', + claimLikelyFalse: 'Likely False', + claimLikelyTrue: 'Likely True', + claimPartiallyVerified: 'Partially Verified', + claimUnverified: 'Unverified', + claimNotVerifiable: 'Not Verifiable', + claimOpinion: 'Opinion', + + // Claim Type Labels + claimFactual: 'Factual', + claimCausal: 'Causal', + claimPredictive: 'Predictive', + claimQuote: 'Quote', + claimStatistical: 'Statistical', + claimOpinionType: 'Opinion', + claimOpinionAsFact: 'Opinion as Fact', + claimHistorical: 'Historical', + claimAttribution: 'Attribution', + claimRE: 'RE', + + // AI Detection Card + aiProbability: 'AI Probability', + verdictLikelyAi: 'LIKELY AI', + verdictPossiblyAi: 'POSSIBLY AI', + verdictMixed: 'MIXED', + verdictLikelyHuman: 'LIKELY HUMAN', + verdictUnknown: 'UNKNOWN', + aiDisclosureFound: 'AI Disclosure Found', + indicators: 'Indicators', + + // Domain Card + domainAnalysis: 'Domain Analysis', + trustScore: 'Trust Score', + domainAge: 'Domain Age', + days: 'days', + ssl: 'SSL', + sslValid: 'Valid', + sslInvalid: 'Invalid', + blacklisted: 'Blacklisted', + yes: 'Yes', + no: 'No', + registrar: 'Registrar', + organization: 'Organization', + country: 'Country', + redFlags: 'Red Flags', + warnings: 'Warnings', + + // Source Assessment Card + risk: 'Risk', + publication: 'Publication', + author: 'Author', + platform: 'Platform', + domain: 'Domain', + confirmed: 'Confirmed', + unconfirmed: 'Unconfirmed', + scoreBreakdown: 'Score Breakdown', + + // Techniques Card + manipulationScore: 'Manipulation Score', + techniquesDetected: '{count} techniques detected', + noTechniquesDetected: 'No manipulation techniques detected', + tapForDetails: 'Tap for details', + tapToCollapse: 'Tap to collapse', + severity: 'Severity', + description: 'Description', + + // Virality Card + viralityPotential: 'Virality Potential', + showFactors: 'Show {count} factors', + hideFactors: 'Hide factors', + + // Did You Know Card + didYouKnow: 'Did you know?', + + // Settings + profile: 'Profile', + username: 'Username', + firstName: 'First Name', + lastName: 'Last Name', + email: 'Email', + updatePassword: 'Update your account password', + subscriptionCredits: 'Subscription & Credits', + active: 'Active', + inactive: 'Inactive', + credits: 'Credits', + remaining: 'Remaining', + spent: 'Spent', + planLimits: 'Plan Limits', + creditsCycle: 'Credits/cycle', + maxImages: 'Max images', + videoMinutes: 'Video minutes', + storage: 'Storage', + language: 'Language', + languageSubtitle: 'Choose your preferred language', + english: 'English', + romanian: 'Română', + + // Social Share + socialShare: 'Social Share', + socialShareDesc: 'When enabled, URLs shared from these platforms will be automatically analyzed. When disabled, the URL will be pre-filled but you must tap Analyze manually.', + facebook: 'Facebook', + autoAnalyzeFacebook: 'Auto-analyze shared Facebook links', + xTwitter: 'X (Twitter)', + autoAnalyzeX: 'Auto-analyze shared X/Twitter links', + tiktok: 'TikTok', + autoAnalyzeTiktok: 'Auto-analyze shared TikTok links', + socialShareNote: 'Share a URL from any social app and didi will appear in your device\'s share menu. Requires an EAS build — not available in Expo Go.', + + // User Profile Widget + creditsLabel: '{count} credits', + perMonth: '/mo', + + // Notifications + analysisComplete: 'Analysis Complete', + notifRiskScore: 'Risk: {score}/100 ({category})', + tapToViewResults: 'Tap to view results', + + // Validation + valMinChars: 'Minimum 20 characters ({len}/20)', + valMaxChars: 'Maximum 50,000 characters exceeded', + valUrlTooLong: 'URL too long', + valUrlMustStart: 'URL must start with http://, https://, or www.', + valInvalidUrl: 'Invalid URL format', + valUrlHostname: 'URL must contain a hostname', + valUrlNotAllowed: 'URL not allowed', + valUrlValidDomain: 'URL must contain a valid domain', + valUrlTld: 'URL must have a valid top-level domain', + valUrlInvalidHost: 'URL contains an invalid hostname segment', + valInvalidDomain: 'Invalid domain format', + valFileEmpty: 'File is empty', + valFileTooLarge: 'File too large ({size}). Maximum: {maxSize}', + valFileTypeNotAllowed: 'File type .{ext} is not allowed', + valAcceptedFormats: 'Accepted {category} formats: {formats}', + valAudioTooLong: 'Audio too long ({duration}s). Maximum: {max}s', + valVideoTooLong: 'Video too long ({duration}s). Maximum: {max}s', + + // Share Action + analyzeUrl: 'Analyze URL', + analyzeText: 'Analyze Text', + analyzeImage: 'Analyze Image', + chooseAnalysisType: 'Choose analysis type', + url: 'URL', + sharedTextLabel: 'Shared text', + sharedImageLabel: 'Shared image', + domainAnalysisSkill: 'Domain Analysis', + checkDomainReputation: 'Check website trust and domain reputation', + + // Upload consent + uploadConsentTitle: 'Consent to media upload', + uploadConsentBody: 'To analyze this file, it will be uploaded to the DiDi analysis servers. The file is used exclusively to run the requested analysis, is processed confidentially and is not shared with third parties. You can delete the analysis (and the associated content) at any time from History.', + uploadConsentCheckbox: 'I agree that my file will be uploaded and processed for analysis, as described in the privacy note above.', + uploadConsentAccept: 'I agree, continue', + uploadConsentReminder: 'By submitting, your file is uploaded for analysis according to your previously given consent.', + + // Dimension Names + dimContentPackaging: 'Content & Packaging', + dimSourceAttribution: 'Source & Attribution', + dimMediaSynthetic: 'Media & Synthetic', + dimEmotionalPsych: 'Emotional & Psychological', + dimSocialNetwork: 'Social & Network', + dimPlatformTech: 'Platform & Technical', + dimTemporalContext: 'Temporal & Context', + dimTargetingVuln: 'Targeting & Vulnerability', + + // Editorial layout + inputLabel: 'Content ({type})', + inputAnalyzed: 'Analyzed input', + riskAnalyzed: 'Risk analyzed', + topIssue: 'Top issue', + otherSignals: 'Other signals', + technicalDetails: 'Technical details', + doNotShare: 'Do not share', + readCritically: 'Read critically', + doNotShareHeadline: 'Do not share this article', + readCarefullyHeadline: 'Read carefully', + noteHeadline: 'Note', + showMore: 'Show more', + hide: 'Hide', + showOtherSignals: 'Show {count} more {plural}', + hideOtherSignals: 'Hide other signals', + cleanContent: 'Clean content', + noManipulationDetected: 'No manipulation techniques detected', + noClaimsToVerify: 'No verifiable claims found', + trustedSourceLabel: 'Trusted', + neutralSourceLabel: 'Neutral', + suspiciousSourceLabel: 'Suspicious', + untrustedSourceLabel: 'Untrusted', + withAiDisclosure: 'with AI disclosure', + withoutAiDisclosure: 'without AI disclosure', +}; + +export type TranslationKeys = keyof typeof en; +export default en; diff --git a/android/didi-app-source/src/i18n/enums.ts b/android/didi-app-source/src/i18n/enums.ts new file mode 100644 index 0000000..f80b02f --- /dev/null +++ b/android/didi-app-source/src/i18n/enums.ts @@ -0,0 +1,117 @@ +/** Human-readable English labels for backend enum codes (Strat 3) */ +export const ENUM_EN: Record = { + // Risk categories + DISINFORMATION: 'Disinformation', + UNRELIABLE: 'Unreliable', + QUESTIONABLE: 'Questionable', + UNCERTAIN: 'Uncertain', + MIXED: 'Mixed', + MOSTLY_TRUSTWORTHY: 'Mostly Trustworthy', + MOSTLY_RELIABLE: 'Mostly Reliable', + TRUSTWORTHY: 'Trustworthy', + RELIABLE: 'Reliable', + INCONCLUSIVE: 'Inconclusive', + + // Severity / confidence levels + CRITICAL: 'Critical', + VERY_HIGH: 'Very High', + HIGH: 'High', + MODERATE: 'Moderate', + MEDIUM: 'Medium', + LOW: 'Low', + VERY_LOW: 'Very Low', + + // AI detection verdicts + LIKELY_AI: 'Likely AI', + POSSIBLY_AI: 'Possibly AI', + UNLIKELY_AI: 'Unlikely AI', + POSSIBLY_HUMAN: 'Possibly Human', + LIKELY_HUMAN: 'Likely Human', + HUMAN: 'Human', + + // Source assessment verdicts + TRUSTED: 'Trusted', + NEUTRAL: 'Neutral', + SUSPICIOUS: 'Suspicious', + UNTRUSTED: 'Untrusted', + + // Claim source stance + SUPPORTS: 'Supports', + CONTRADICTS: 'Contradicts', + + // Source reliability + official: 'Official', + news: 'News', + blog: 'Blog', + unknown: 'Unknown', + + // Recommended actions + NONE: 'None', + CAUTION: 'Caution', + WARNING: 'Warning', + URGENT: 'Urgent', + ESCALATE: 'Escalate', + REVIEW: 'Review', + + // Virality + VIRAL: 'Viral', +}; + +/** Romanian translations for fixed backend enum values (Strat 3) */ +export const ENUM_RO: Record = { + // Risk categories + DISINFORMATION: 'Dezinformare', + UNRELIABLE: 'Nedemn de încredere', + QUESTIONABLE: 'Îndoielnic', + UNCERTAIN: 'Incert', + MIXED: 'Mixt', + MOSTLY_TRUSTWORTHY: 'Preponderent credibil', + MOSTLY_RELIABLE: 'Preponderent credibil', + TRUSTWORTHY: 'Demn de încredere', + RELIABLE: 'De încredere', + INCONCLUSIVE: 'Neconcludent', + + // Severity / confidence levels + CRITICAL: 'Critic', + VERY_HIGH: 'Foarte ridicat', + HIGH: 'Ridicat', + MODERATE: 'Moderat', + MEDIUM: 'Mediu', + LOW: 'Scăzut', + VERY_LOW: 'Foarte scăzut', + + // AI detection verdicts + LIKELY_AI: 'Probabil AI', + POSSIBLY_AI: 'Posibil AI', + UNLIKELY_AI: 'Improbabil AI', + POSSIBLY_HUMAN: 'Posibil uman', + LIKELY_HUMAN: 'Probabil uman', + HUMAN: 'Uman', + + // Source assessment verdicts + TRUSTED: 'De încredere', + NEUTRAL: 'Neutru', + SUSPICIOUS: 'Suspect', + UNTRUSTED: 'Nedemn de încredere', + + // Claim source stance + SUPPORTS: 'Susține', + CONTRADICTS: 'Contrazice', + + // Source reliability + official: 'Oficial', + news: 'Știri', + blog: 'Blog', + unknown: 'Necunoscut', + + // Recommended actions + NONE: 'Niciuna', + CAUTION: 'Precauție', + WARNING: 'Avertizare', + URGENT: 'Urgent', + ESCALATE: 'Escaladează', + REVIEW: 'Revizuire', + + // Virality + VIRAL: 'Viral', +}; diff --git a/android/didi-app-source/src/i18n/index.ts b/android/didi-app-source/src/i18n/index.ts new file mode 100644 index 0000000..1203d6b --- /dev/null +++ b/android/didi-app-source/src/i18n/index.ts @@ -0,0 +1,149 @@ +import { create } from 'zustand'; +import { Platform } from 'react-native'; +import en from './en'; +import ro from './ro'; +import { ENUM_EN, ENUM_RO } from './enums'; +import type { TranslationKeys } from './en'; + +export type Language = 'en' | 'ro'; + +const translations: Record> = { en, ro }; + +// Storage helpers (same pattern as authStore) +const storage = { + getItem: (key: string): Promise => { + if (Platform.OS === 'web') return Promise.resolve(localStorage.getItem(key)); + return require('expo-secure-store').getItemAsync(key); + }, + setItem: (key: string, value: string): Promise => { + if (Platform.OS === 'web') { localStorage.setItem(key, value); return Promise.resolve(); } + return require('expo-secure-store').setItemAsync(key, value); + }, +}; + +interface LanguageState { + language: Language; + setLanguage: (lang: Language) => Promise; + loadLanguage: () => Promise; +} + +export const useLanguageStore = create((set) => ({ + language: 'en', + + setLanguage: async (lang: Language) => { + set({ language: lang }); + await storage.setItem('app_language', lang); + }, + + loadLanguage: async () => { + try { + const saved = await storage.getItem('app_language'); + if (saved === 'ro' || saved === 'en') { + set({ language: saved }); + } + } catch { + // Default to 'en' + } + }, +})); + +/** + * Simple interpolation: replaces {key} with values. + * t('hello {name}', { name: 'World' }) => 'hello World' + */ +function interpolate(template: string, vars?: Record): string { + if (!vars) return template; + return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? `{${key}}`)); +} + +/** Hook that returns the translation function */ +export function useTranslation() { + const language = useLanguageStore((s) => s.language); + const dict = translations[language]; + + function t(key: TranslationKeys, vars?: Record): string { + return interpolate(dict[key] ?? en[key] ?? key, vars); + } + + return { t, language }; +} + +/** Non-hook version for use outside components (utils, etc.) */ +export function getTranslation() { + const language = useLanguageStore.getState().language; + const dict = translations[language]; + + function t(key: TranslationKeys, vars?: Record): string { + return interpolate(dict[key] ?? en[key] ?? key, vars); + } + + return { t, language }; +} + +/** + * Strat 2: Pick the right _ro/_en field from backend bilingual data. + * + * localized(obj, 'name') with lang='ro' tries: + * obj.name_ro → obj.name_en → obj.name + */ +export function localized( + obj: Record | null | undefined, + ...fields: string[] +): string { + if (!obj) return ''; + const lang = useLanguageStore.getState().language; + const other = lang === 'ro' ? 'en' : 'ro'; + + for (const field of fields) { + if (obj[`${field}_${lang}`]) return obj[`${field}_${lang}`]; + } + for (const field of fields) { + if (obj[`${field}_${other}`]) return obj[`${field}_${other}`]; + } + for (const field of fields) { + if (obj[field]) return obj[field]; + } + return ''; +} + +/** + * Strat 3: Translate fixed backend enum values to human-readable labels. + * Looks up in language-specific dictionary; falls back to humanizing the code + * (LIKELY_HUMAN → "Likely Human") so unknown codes never leak raw underscores. + */ +function humanizeEnum(value: string): string { + return value + .split('_') + .map((w) => { + const lower = w.toLowerCase(); + // Preserve common acronyms + if (['ai', 'url', 'id', 'ui', 'api'].includes(lower)) return w.toUpperCase(); + return lower.charAt(0).toUpperCase() + lower.slice(1); + }) + .join(' '); +} + +export function enumLabel(value: string | null | undefined): string { + if (!value) return ''; + const lang = useLanguageStore.getState().language; + const dict = lang === 'ro' ? ENUM_RO : ENUM_EN; + if (dict[value]) return dict[value]; + // Fallback: humanize unknown codes so they don't leak raw to UI + return humanizeEnum(value); +} + +/** + * Strat 4: Smooth out raw enum codes embedded in narrative text from backend. + * Catches patterns like LIKELY_HUMAN, AI_GENERATED, HIGH_RISK left in TL;DR + * or finding quotes and replaces them with the localized label (or humanized + * fallback). Only triggers on ALL_CAPS_WITH_UNDERSCORES tokens. + */ +export function smoothEnums(text: string | null | undefined): string { + if (!text) return ''; + return text.replace(/\b[A-Z][A-Z0-9]+(?:_[A-Z0-9]+)+\b/g, (match) => { + const lang = useLanguageStore.getState().language; + const dict = lang === 'ro' ? ENUM_RO : ENUM_EN; + if (dict[match]) return dict[match]; + return humanizeEnum(match); + }); +} diff --git a/android/didi-app-source/src/i18n/ro.ts b/android/didi-app-source/src/i18n/ro.ts new file mode 100644 index 0000000..54f868d --- /dev/null +++ b/android/didi-app-source/src/i18n/ro.ts @@ -0,0 +1,344 @@ +import type { TranslationKeys } from './en'; + +const ro: Record = { + // Common + back: 'Înapoi', + cancel: 'Anulează', + delete: 'Șterge', + error: 'Eroare', + loading: 'Se încarcă...', + goBack: 'Înapoi', + settings: 'Setări', + signOut: 'Deconectare', + signIn: 'Autentificare', + createAccount: 'Creează cont', + analyze: 'Analizează', + confirm: 'Confirmă', + + // Main / Auth + appName: 'didi', + appSubtitle: 'Instrument de analiză a conținutului', + authenticating: 'Se autentifică...', + redirectingToLogin: 'Redirecționare spre autentificare...', + authError: 'Eroare de autentificare', + authDenied: 'Autorizarea a fost refuzată. Încercați din nou.', + securityError: 'Eroare de securitate', + invalidAuthResponse: 'Răspuns de autentificare invalid. Încercați din nou.', + noAuthCode: 'Nu s-a primit codul de autorizare', + authFailed: 'Autentificare eșuată', + authFailedMessage: 'Nu s-a putut finaliza autentificarea. Încercați din nou.', + changePassword: 'Schimbă parola', + redirecting: 'Redirecționare...', + + // Dashboard + analysis: 'Analiză', + fullPipelineAnalysis: 'Analiză completă', + skills: 'Componente', + individualComponents: 'Componente individuale de analiză', + history: 'Istoric', + pastAnalyses: 'Analize anterioare', + accountPreferences: 'Cont și preferințe', + signOutConfirm: 'Sigur doriți să vă deconectați?', + signOutSubtitle: 'Deconectați-vă de la cont pe acest dispozitiv', + + // Pipeline + fullAnalysis: 'Analiză completă', + runAllComponents: 'Rulează toate componentele simultan', + textToAnalyze: 'Text de analizat', + enterTextHint: 'Introduceți text, articol sau conținut', + pasteTextPlaceholder: 'Lipiți textul aici...', + characters: 'caractere', + urlToAnalyze: 'URL de analizat', + enterUrlHint: 'Introduceți un URL de pagină web', + urlPlaceholder: 'https://exemplu.com/articol', + imageToAnalyze: 'Imagine de analizat', + takePhotoOrSelect: 'Faceți o fotografie sau selectați din galerie', + gallery: 'Galerie', + camera: 'Cameră', + audioToAnalyze: 'Audio de analizat', + selectAudioHint: 'Selectați un fișier audio pentru analiză', + selectAudio: 'Selectează audio', + videoToAnalyze: 'Video de analizat', + selectVideoHint: 'Selectați un fișier video pentru analiză', + selectVideo: 'Selectează video', + runFullAnalysis: 'Rulează analiza completă', + submitting: 'Se trimite...', + uploadingMedia: 'Se încarcă media...', + startingAnalysis: 'Se pornește analiza...', + invalidFile: 'Fișier invalid', + permissionRequired: 'Permisiune necesară', + cameraPermission: 'Este necesar accesul la cameră pentru a face fotografii.', + analysisError: 'Eroare de analiză', + failedToStartAnalysis: 'Nu s-a putut porni analiza.', + contentUnavailable: 'Conținut indisponibil', + contentNotExtracted: 'Conținutul nu a putut fi extras din acest URL.', + uploadManually: 'Încarcă manual', + + // Tabs + tabText: 'Text', + tabImage: 'Imagine', + tabAudio: 'Audio', + tabVideo: 'Video', + tabUrl: 'URL', + + // Pipeline Results + analysisResults: 'Rezultate analiză', + processingMedia: 'Se procesează media...', + generatingVerdict: 'Se generează verdictul...', + processingProgress: 'Procesare ({done}/{total})...', + percentComplete: '{percent}% finalizat', + analyzing: 'Se analizează...', + noResults: 'Nu sunt rezultate disponibile', + analysisTimedOut: 'Analiza a expirat. Încercați din nou.', + connectionLost: 'Conexiune pierdută. Verificați rețeaua și încercați din nou.', + serverError: 'Eroare server. Încercați mai târziu.', + sessionNotFound: 'Sesiunea de analiză nu a fost găsită.', + + // Component Labels + manipulationTechniques: 'Tehnici de manipulare', + claimsVerification: 'Verificare afirmații', + aiDetection: 'Detecție IA', + sourceAssessment: 'Evaluare sursă', + contextAnalysis: 'Analiză context', + + // Skills Screen + analyzeIndividualAspects: 'Analizează aspecte individuale', + detectManipulation: 'Detectează tactici de persuasiune și manipulare', + evaluateSource: 'Evaluează credibilitatea și încrederea sursei', + aiTamperDetection: 'Detecție manipulare IA', + detectAiContent: 'Detectează conținut generat sau manipulat de IA', + claimsAnalysis: 'Analiză afirmații', + verifyClaims: 'Verifică afirmațiile și credibilitatea', + enterTextToAnalyze: 'Introduceți text pentru analiză', + + // History + noAnalysesYet: 'Nicio analiză încă', + runFirstAnalysis: 'Rulează prima analiză pentru a o vedea aici', + justNow: 'Chiar acum', + minutesAgo: 'acum {min}m', + hoursAgo: 'acum {hr}h', + daysAgo: 'acum {day}z', + mediaAnalysis: '(analiză media)', + deleteAnalysis: 'Șterge analiza', + deleteConfirm: 'Sigur doriți să ștergeți această analiză?', + failedToLoadHistory: 'Nu s-a putut încărca istoricul', + failedToLoadDetails: 'Nu s-au putut încărca detaliile', + failedToDelete: 'Nu s-a putut șterge', + + // Verdict Card + verdict: 'Verdict', + riskScore: 'Scor de risc', + confidence: 'Încredere', + recommendedAction: 'Acțiune recomandată', + + // Component Scores + componentScores: 'Scoruri componente', + scoreManipulation: 'Manipulare', + scoreClaims: 'Afirmații', + scoreAiGenerated: 'Generat IA', + scoreSource: 'Sursă', + scoreContext: 'Context', + + // Claims Card + credibilityScore: 'Scor credibilitate', + total: 'Total', + trueLabel: 'Adevărat', + falseLabel: 'Fals', + unverified: 'Neverificat', + sources: 'Surse', + stance: 'Poziție', + + // Claim Status Labels + claimVerifiedTrue: 'Verificat adevărat', + claimVerifiedFalse: 'Verificat fals', + claimLikelyFalse: 'Probabil fals', + claimLikelyTrue: 'Probabil adevărat', + claimPartiallyVerified: 'Parțial verificat', + claimUnverified: 'Neverificat', + claimNotVerifiable: 'Neverificabil', + claimOpinion: 'Opinie', + + // Claim Type Labels + claimFactual: 'Factual', + claimCausal: 'Cauzal', + claimPredictive: 'Predictiv', + claimQuote: 'Citat', + claimStatistical: 'Statistic', + claimOpinionType: 'Opinie', + claimOpinionAsFact: 'Opinie ca fapt', + claimHistorical: 'Istoric', + claimAttribution: 'Atribuire', + claimRE: 'RE', + + // AI Detection Card + aiProbability: 'Probabilitate IA', + verdictLikelyAi: 'PROBABIL IA', + verdictPossiblyAi: 'POSIBIL IA', + verdictMixed: 'MIXT', + verdictLikelyHuman: 'PROBABIL UMAN', + verdictUnknown: 'NECUNOSCUT', + aiDisclosureFound: 'Declarație IA găsită', + indicators: 'Indicatori', + + // Domain Card + domainAnalysis: 'Analiză domeniu', + trustScore: 'Scor încredere', + domainAge: 'Vârsta domeniului', + days: 'zile', + ssl: 'SSL', + sslValid: 'Valid', + sslInvalid: 'Invalid', + blacklisted: 'Pe lista neagră', + yes: 'Da', + no: 'Nu', + registrar: 'Registrar', + organization: 'Organizație', + country: 'Țară', + redFlags: 'Semnale de alarmă', + warnings: 'Avertismente', + + // Source Assessment Card + risk: 'Risc', + publication: 'Publicație', + author: 'Autor', + platform: 'Platformă', + domain: 'Domeniu', + confirmed: 'Confirmat', + unconfirmed: 'Neconfirmat', + scoreBreakdown: 'Detalii scor', + + // Techniques Card + manipulationScore: 'Scor manipulare', + techniquesDetected: '{count} tehnici detectate', + noTechniquesDetected: 'Nu s-au detectat tehnici de manipulare', + tapForDetails: 'Apasă pentru detalii', + tapToCollapse: 'Apasă pentru a restrânge', + severity: 'Severitate', + description: 'Descriere', + + // Virality Card + viralityPotential: 'Potențial de viralitate', + showFactors: 'Arată {count} factori', + hideFactors: 'Ascunde factorii', + + // Did You Know Card + didYouKnow: 'Știați că?', + + // Settings + profile: 'Profil', + username: 'Nume utilizator', + firstName: 'Prenume', + lastName: 'Nume', + email: 'Email', + updatePassword: 'Actualizați parola contului', + subscriptionCredits: 'Abonament și credite', + active: 'Activ', + inactive: 'Inactiv', + credits: 'Credite', + remaining: 'Rămase', + spent: 'Cheltuite', + planLimits: 'Limite plan', + creditsCycle: 'Credite/ciclu', + maxImages: 'Max imagini', + videoMinutes: 'Minute video', + storage: 'Stocare', + language: 'Limbă', + languageSubtitle: 'Alegeți limba preferată', + english: 'English', + romanian: 'Română', + + // Social Share + socialShare: 'Partajare socială', + socialShareDesc: 'Când este activat, URL-urile partajate de pe aceste platforme vor fi analizate automat. Când este dezactivat, URL-ul va fi pre-completat dar trebuie să apăsați Analizează manual.', + facebook: 'Facebook', + autoAnalyzeFacebook: 'Analizează automat linkurile Facebook partajate', + xTwitter: 'X (Twitter)', + autoAnalyzeX: 'Analizează automat linkurile X/Twitter partajate', + tiktok: 'TikTok', + autoAnalyzeTiktok: 'Analizează automat linkurile TikTok partajate', + socialShareNote: 'Partajați un URL din orice aplicație socială și didi va apărea în meniul de partajare al dispozitivului. Necesită un build EAS — nu este disponibil în Expo Go.', + + // User Profile Widget + creditsLabel: '{count} credite', + perMonth: '/lună', + + // Notifications + analysisComplete: 'Analiză finalizată', + notifRiskScore: 'Risc: {score}/100 ({category})', + tapToViewResults: 'Apasă pentru a vedea rezultatele', + + // Validation + valMinChars: 'Minim 20 caractere ({len}/20)', + valMaxChars: 'S-a depășit limita de 50.000 caractere', + valUrlTooLong: 'URL prea lung', + valUrlMustStart: 'URL-ul trebuie să înceapă cu http://, https:// sau www.', + valInvalidUrl: 'Format URL invalid', + valUrlHostname: 'URL-ul trebuie să conțină un hostname', + valUrlNotAllowed: 'URL neacceptat', + valUrlValidDomain: 'URL-ul trebuie să conțină un domeniu valid', + valUrlTld: 'URL-ul trebuie să aibă un domeniu de nivel superior valid', + valUrlInvalidHost: 'URL-ul conține un segment de hostname invalid', + valInvalidDomain: 'Format domeniu invalid', + valFileEmpty: 'Fișierul este gol', + valFileTooLarge: 'Fișier prea mare ({size}). Maxim: {maxSize}', + valFileTypeNotAllowed: 'Tipul de fișier .{ext} nu este permis', + valAcceptedFormats: 'Formate {category} acceptate: {formats}', + valAudioTooLong: 'Audio prea lung ({duration}s). Maxim: {max}s', + valVideoTooLong: 'Video prea lung ({duration}s). Maxim: {max}s', + + // Share Action + analyzeUrl: 'Analizează URL', + analyzeText: 'Analizează textul', + analyzeImage: 'Analizează imaginea', + chooseAnalysisType: 'Alege tipul de analiză', + url: 'URL', + sharedTextLabel: 'Text partajat', + sharedImageLabel: 'Imagine partajată', + domainAnalysisSkill: 'Analiză domeniu', + checkDomainReputation: 'Verifică încrederea și reputația domeniului', + + // Upload consent + uploadConsentTitle: 'Consimțământ pentru încărcarea fișierelor', + uploadConsentBody: 'Pentru a analiza acest fișier, el va fi încărcat pe serverele de analiză DiDi. Fișierul este folosit exclusiv pentru rularea analizei solicitate, este procesat confidențial și nu este partajat cu terți. Puteți șterge oricând analiza (și conținutul asociat) din Istoric.', + uploadConsentCheckbox: 'Sunt de acord ca fișierul meu să fie încărcat și procesat pentru analiză, conform notei de confidențialitate de mai sus.', + uploadConsentAccept: 'Sunt de acord, continuă', + uploadConsentReminder: 'Prin trimitere, fișierul este încărcat pentru analiză conform consimțământului acordat anterior.', + + // Dimension Names + dimContentPackaging: 'Conținut și prezentare', + dimSourceAttribution: 'Sursă și atribuire', + dimMediaSynthetic: 'Media și sintetice', + dimEmotionalPsych: 'Emoțional și psihologic', + dimSocialNetwork: 'Social și rețea', + dimPlatformTech: 'Platformă și tehnic', + dimTemporalContext: 'Temporal și context', + dimTargetingVuln: 'Țintire și vulnerabilitate', + + // Editorial layout + inputLabel: 'Conținut ({type})', + inputAnalyzed: 'Conținut analizat', + riskAnalyzed: 'Risc analizat', + topIssue: 'Cea mai gravă problemă', + otherSignals: 'Alte semne', + technicalDetails: 'Detalii tehnice', + doNotShare: 'Nu distribui', + readCritically: 'Citește critic', + doNotShareHeadline: 'Nu distribui acest articol', + readCarefullyHeadline: 'Citește cu atenție', + noteHeadline: 'De reținut', + showMore: 'Vezi mai mult', + hide: 'Ascunde', + showOtherSignals: 'Vezi încă {count} {plural}', + hideOtherSignals: 'Ascunde alte semne', + cleanContent: 'Conținut curat', + noManipulationDetected: 'Nicio tehnică de manipulare detectată', + noClaimsToVerify: 'Nicio afirmație verificabilă', + trustedSourceLabel: 'De încredere', + neutralSourceLabel: 'Neutră', + suspiciousSourceLabel: 'Suspectă', + untrustedSourceLabel: 'Neîncredere', + withAiDisclosure: 'cu declarație AI', + withoutAiDisclosure: 'fără declarație AI', +}; + +export default ro; diff --git a/android/didi-app-source/src/navigation/AppNavigator.tsx b/android/didi-app-source/src/navigation/AppNavigator.tsx new file mode 100644 index 0000000..303eded --- /dev/null +++ b/android/didi-app-source/src/navigation/AppNavigator.tsx @@ -0,0 +1,163 @@ +import React, { useEffect, useRef } from 'react'; +import { View, Platform, StyleSheet, AppState } from 'react-native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +import MainScreen from '../screens/auth/MainScreen'; +import LoginScreen from '../screens/auth/LoginScreen'; +import RegisterScreen from '../screens/auth/RegisterScreen'; +import DashboardScreen from '../screens/DashboardScreen'; +import PipelineScreen from '../screens/PipelineScreen'; +import PipelineResultsScreen from '../screens/PipelineResultsScreen'; +import SkillsScreen from '../screens/SkillsScreen'; +import SkillAnalysisScreen from '../screens/SkillAnalysisScreen'; +import HistoryScreen from '../screens/HistoryScreen'; +import SettingsScreen from '../screens/SettingsScreen'; +import ChangePasswordScreen from '../screens/ChangePasswordScreen'; +import ShareActionScreen from '../screens/ShareActionScreen'; +import type { AnalysisSession } from '../types/analysis'; +import { useAuthStore } from '../store/authStore'; +import { useLanguageStore } from '../i18n'; +import { useSharedContent } from '../hooks/useShareIntent'; +import { savePendingShare } from '../utils/socialShareStorage'; +import { sharedPayloadToParams, type SharedImage } from '../utils/shareIntent'; +import * as Notifications from 'expo-notifications'; + +export type SharedContentParams = { + sharedUrl?: string; + sharedText?: string; + sharedImage?: SharedImage; +}; + +export type RootStackParamList = { + Main: undefined; + Login: undefined; + Register: undefined; + Dashboard: undefined; + Pipeline: SharedContentParams | undefined; + PipelineResults: { sessionId: string; components?: string[] } | { result: AnalysisSession }; + Skills: undefined; + SkillAnalysis: { skillType: string } & SharedContentParams; + ShareAction: SharedContentParams; + History: undefined; + Settings: undefined; + ChangePassword: undefined; +}; + +const Stack = createNativeStackNavigator(); + +export const navigationRef = React.createRef>(); + +export default function AppNavigator() { + const { isAuthenticated, isLoading, loadTokens } = useAuthStore(); + const { sharedPayload, resetShareIntent } = useSharedContent(); + const handledShareRef = useRef(null); + const wasAuthenticatedRef = useRef(false); + + // Load tokens and language from storage on cold start + useEffect(() => { + loadTokens(); + useLanguageStore.getState().loadLanguage(); + }, []); + + // When isAuthenticated flips to false (token refresh failed), kick to login + useEffect(() => { + if (wasAuthenticatedRef.current && !isAuthenticated && navigationRef.current) { + navigationRef.current.reset({ index: 0, routes: [{ name: 'Main' }] }); + } + wasAuthenticatedRef.current = isAuthenticated; + }, [isAuthenticated]); + + // Re-validate tokens when app returns from background + useEffect(() => { + const sub = AppState.addEventListener('change', (state) => { + if (state === 'active' && useAuthStore.getState().isAuthenticated) { + const { expiresAt, refreshAccessToken } = useAuthStore.getState(); + // If access token expired or about to expire, refresh proactively + if (expiresAt && Date.now() > expiresAt - 30000) { + refreshAccessToken(); + } + } + }); + return () => sub.remove(); + }, []); + + useEffect(() => { + if (!sharedPayload) return; + + // Dedup key: URL, text content, or image URI + const shareKey = + sharedPayload.kind === 'url' ? `url:${sharedPayload.url}` + : sharedPayload.kind === 'text' ? `text:${sharedPayload.text}` + : `image:${sharedPayload.image.uri}`; + if (shareKey === handledShareRef.current) return; + handledShareRef.current = shareKey; + + if (isAuthenticated && navigationRef.current) { + // Show analysis type picker + navigationRef.current.navigate('ShareAction', sharedPayloadToParams(sharedPayload)); + } else { + // Store for after auth (or if navigator not ready) + savePendingShare(sharedPayload); + } + + resetShareIntent(); + }, [sharedPayload, isAuthenticated, resetShareIntent]); + + // Handle notification tap — navigate to analysis results + useEffect(() => { + const sub = Notifications.addNotificationResponseReceivedListener((response) => { + const data = response.notification.request.content.data; + const sessionId = data?.sessionId as string | undefined; + if (sessionId && navigationRef.current && isAuthenticated) { + (navigationRef.current as any).push('PipelineResults', { sessionId }); + } + }); + return () => sub.remove(); + }, [isAuthenticated]); + + const content = ( + + + {/* Auth */} + + + + + {/* App */} + + + + + + + + + + + + ); + + if (Platform.OS === 'web') { + return ( + + {content} + + ); + } + + return content; +} + +const webStyles = StyleSheet.create({ + outer: { + flex: 1, + alignItems: 'center', + backgroundColor: '#0d1424', + }, + inner: { + flex: 1, + width: '100%', + maxWidth: 480, + }, +}); diff --git a/android/didi-app-source/src/screens/ChangePasswordScreen.tsx b/android/didi-app-source/src/screens/ChangePasswordScreen.tsx new file mode 100644 index 0000000..ad61eb4 --- /dev/null +++ b/android/didi-app-source/src/screens/ChangePasswordScreen.tsx @@ -0,0 +1,220 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ActivityIndicator, + StatusBar, + Platform, +} from 'react-native'; +import * as Crypto from 'expo-crypto'; +import { useNavigation } from '@react-navigation/native'; +import { COLORS, SPACING, FONT_SIZES, FONT_WEIGHTS } from '../theme/colors'; +import { AUTH_CONFIG, getRedirectUri } from '../api/config'; + +const WebView = Platform.OS !== 'web' + ? require('react-native-webview').WebView + : null; +import { exchangeCodeForTokens } from '../api/auth'; +import { useAuthStore } from '../store/authStore'; +import { useTranslation } from '../i18n'; + +function generateCodeVerifier(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; + const randomBytes = Crypto.getRandomBytes(43); + let result = ''; + for (let i = 0; i < 43; i++) { + result += chars[randomBytes[i] % chars.length]; + } + return result; +} + +function generateState(): string { + const bytes = Crypto.getRandomBytes(24); + return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); +} + +export default function ChangePasswordScreen() { + const { t } = useTranslation(); + const navigation = useNavigation(); + const { setTokens } = useAuthStore(); + const [webviewLoading, setWebviewLoading] = useState(true); + const [codeVerifier] = useState(generateCodeVerifier); + const [codeChallenge, setCodeChallenge] = useState(null); + const [oauthState] = useState(generateState); + const webviewRef = useRef(null); + const handledRedirectRef = useRef(false); + + useEffect(() => { + (async () => { + const digest = await Crypto.digestStringAsync( + Crypto.CryptoDigestAlgorithm.SHA256, + codeVerifier, + { encoding: Crypto.CryptoEncoding.BASE64 } + ); + const challenge = digest + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + setCodeChallenge(challenge); + })(); + }, [codeVerifier]); + + const redirectUri = getRedirectUri(); + + const passwordUrl = codeChallenge + ? `${AUTH_CONFIG.AUTH_URL}` + + `?client_id=${AUTH_CONFIG.CLIENT_ID}` + + `&redirect_uri=${encodeURIComponent(redirectUri)}` + + `&response_type=code` + + `&scope=openid` + + `&code_challenge=${codeChallenge}` + + `&code_challenge_method=S256` + + `&state=${oauthState}` + + `&kc_action=UPDATE_PASSWORD` + : null; + + // Web: redirect browser directly + useEffect(() => { + if (Platform.OS === 'web' && passwordUrl) { + sessionStorage.setItem('pkce_code_verifier', codeVerifier); + sessionStorage.setItem('oauth_state', oauthState); + sessionStorage.setItem('password_change', 'true'); + window.location.replace(passwordUrl); + } + }, [passwordUrl]); + + const handleNavigationChange = async (navState: { url: string }) => { + const { url } = navState; + + if (url.startsWith(redirectUri)) { + // Prevent double-fire from onNavigationStateChange + onShouldStartLoadWithRequest + if (handledRedirectRef.current) return; + handledRedirectRef.current = true; + + const codeMatch = url.match(/[?&]code=([^&]+)/); + const stateMatch = url.match(/[?&]state=([^&]+)/); + + if (codeMatch && stateMatch && stateMatch[1] === oauthState) { + // Keycloak returns a new code after password change — refresh tokens silently + try { + const tokens = await exchangeCodeForTokens(codeMatch[1], codeVerifier); + await setTokens(tokens); + } catch { + // Token refresh can fail silently — user is still logged in + } + } + navigation.goBack(); + } + }; + + return ( + + + + + navigation.goBack()} style={styles.backButton}> + ← Back + + {t('changePassword')} + + + {Platform.OS === 'web' ? ( + + + {t('redirecting')} + + ) : passwordUrl && WebView ? ( + + {webviewLoading && ( + + + + )} + { + if (request.url.startsWith(redirectUri)) { + handleNavigationChange({ url: request.url }); + return false; + } + return true; + }} + onLoadStart={() => setWebviewLoading(true)} + onLoadEnd={() => setWebviewLoading(false)} + style={styles.webview} + originWhitelist={['https://', 'http://', 'didi://']} + javaScriptEnabled + domStorageEnabled + startInLoadingState={false} + incognito + /> + + ) : ( + + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + center: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + paddingTop: SPACING.xxl, + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.md, + backgroundColor: COLORS.bg.primary, + }, + backButton: { + paddingVertical: SPACING.sm, + paddingRight: SPACING.md, + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + }, + headerTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + }, + webviewContainer: { + flex: 1, + }, + webview: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + loadingText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: SPACING.md, + }, + webviewLoading: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + backgroundColor: COLORS.bg.primary, + }, +}); diff --git a/android/didi-app-source/src/screens/DashboardScreen.tsx b/android/didi-app-source/src/screens/DashboardScreen.tsx new file mode 100644 index 0000000..087bf80 --- /dev/null +++ b/android/didi-app-source/src/screens/DashboardScreen.tsx @@ -0,0 +1,180 @@ +import React, { useCallback, useRef, useState } from 'react'; +import { + View, + Text, + ScrollView, + TouchableOpacity, + StyleSheet, + StatusBar, + RefreshControl, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation, useFocusEffect } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../theme/colors'; +import { useAuthStore } from '../store/authStore'; +import { fetchProfile, fetchSubscriptionUsage, fetchCredits } from '../api/auth'; +import GlassCard from '../components/GlassCard'; +import UserProfileWidget from '../components/UserProfileWidget'; +import { useTranslation } from '../i18n'; +import { useSignOut } from '../hooks/useSignOut'; +import type { RootStackParamList } from '../navigation/AppNavigator'; + +export default function DashboardScreen() { + const navigation = useNavigation>(); + const { profile, setProfile } = useAuthStore(); + const { t } = useTranslation(); + const handleSignOut = useSignOut(); + const [usageStats, setUsageStats] = useState(null); + const [creditsData, setCreditsData] = useState<{ credits_remained: number; credits_spent: number } | null>(null); + const [refreshing, setRefreshing] = useState(false); + const mountedRef = useRef(true); + + const loadData = useCallback(async () => { + const [profileData, subData, credits] = await Promise.all([ + fetchProfile().catch(() => null), + fetchSubscriptionUsage().catch(() => null), + fetchCredits().catch(() => null), + ]); + if (!mountedRef.current) return; + if (profileData) setProfile(profileData); + if (subData) setUsageStats(subData); + if (credits) setCreditsData(credits); + }, [setProfile]); + + useFocusEffect( + useCallback(() => { + mountedRef.current = true; + loadData(); + return () => { mountedRef.current = false; }; + }, [loadData]) + ); + + const onRefresh = async () => { + setRefreshing(true); + await loadData(); + if (mountedRef.current) setRefreshing(false); + }; + + return ( + + + + + + } + > + {/* Logo */} + + {t('appName')} + {t('appSubtitle')} + + + {/* Profile Widget - collapsed/expanded */} + + + {/* Feature Cards */} + + navigation.navigate('Pipeline')} + /> + navigation.navigate('Skills')} + /> + navigation.navigate('History')} + /> + navigation.navigate('Settings')} + /> + + + {/* Sign Out */} + + {t('signOut')} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + scrollContent: { + flexGrow: 1, + justifyContent: 'center', + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.lg, + }, + logoContainer: { + alignItems: 'center', + justifyContent: 'center', + marginBottom: SPACING.xl, + }, + logo: { + fontSize: FONT_SIZES.displayMd, + fontWeight: FONT_WEIGHTS.bold, + color: COLORS.text.primary, + letterSpacing: 2, + }, + logoSubtitle: { + fontSize: FONT_SIZES.sm, + color: COLORS.text.secondary, + marginTop: SPACING.xs, + }, + cardsContainer: { + marginBottom: SPACING.lg, + }, + signOutButton: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: SPACING.md, + }, + signOutText: { + color: COLORS.status.red, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/screens/HistoryScreen.tsx b/android/didi-app-source/src/screens/HistoryScreen.tsx new file mode 100644 index 0000000..167ae34 --- /dev/null +++ b/android/didi-app-source/src/screens/HistoryScreen.tsx @@ -0,0 +1,456 @@ +import React, { useState, useCallback, useEffect, useRef } from 'react'; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + StatusBar, + ActivityIndicator, + Alert, + RefreshControl, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation, useFocusEffect } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, +} from '../theme/colors'; +import axios from 'axios'; +import { getHistory, getHistoryDetail, deleteHistoryItem } from '../api/history'; +import { pollQueueStatus } from '../api/analysis'; +import { POLL_CONFIG } from '../api/config'; +import { useAuthStore } from '../store/authStore'; +import { useTranslation } from '../i18n'; +import HistoryCard from '../components/HistoryCard'; +import type { RootStackParamList } from '../navigation/AppNavigator'; +import type { AnalysisSession } from '../types/analysis'; + +const HISTORY_POLL_INTERVAL = 3000; + +interface RunningItemProgress { + progress: number; + completedComponents: string[]; +} + +export default function HistoryScreen() { + const navigation = useNavigation>(); + const { userId } = useAuthStore(); + const { t } = useTranslation(); + + const [items, setItems] = useState([]); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + + // Progress tracking for running items + const [runningProgress, setRunningProgress] = useState>({}); + const pollTimerRef = useRef | null>(null); + const abortRef = useRef(null); + const pollStartTimeRef = useRef(0); + const isFocusedRef = useRef(false); + const itemsRef = useRef([]); + + const loadData = useCallback(async (p: number = 1, replace: boolean = true) => { + if (!userId) return; + try { + const { items: newItems, pagination } = await getHistory(userId, p, 20); + if (replace) { + setItems(newItems); + } else { + setItems((prev) => [...prev, ...newItems]); + } + setPage(pagination.page); + setTotalPages(pagination.pages); + } catch (err: any) { + if (p === 1) { + Alert.alert(t('error'), err?.message || t('failedToLoadHistory')); + } + } + }, [userId]); + + // Get list of running session IDs from current items + const getRunningIds = useCallback((itemsList: AnalysisSession[]): string[] => { + return itemsList + .filter((i) => i.status === 'running' || i.status === 'pending' || i.status === 'processing') + .map((i) => i.session_id); + }, []); + + // Poll running items for progress updates + const pollRunningItems = useCallback(async (runningIds: string[]) => { + if (runningIds.length === 0) return; + + // Check max duration — stop polling zombie sessions + if (pollStartTimeRef.current > 0 && + Date.now() - pollStartTimeRef.current > POLL_CONFIG.MAX_DURATION) { + // Mark all still-running items as stale (completed with whatever data they have) + setItems((prev) => + prev.map((item) => + runningIds.includes(item.session_id) + ? { ...item, status: 'completed' } + : item + ) + ); + setRunningProgress({}); + return; + } + + // Abort previous in-flight requests before starting new batch + if (abortRef.current) { + abortRef.current.abort(); + } + abortRef.current = new AbortController(); + const { signal } = abortRef.current; + + const results = await Promise.allSettled( + runningIds.map((sid) => pollQueueStatus(sid, signal)) + ); + + // If aborted (unfocus/unmount), discard results + if (signal.aborted) return; + + const newProgress: Record = {}; + const completedIds: string[] = []; + + results.forEach((result, idx) => { + const sid = runningIds[idx]; + if (result.status === 'fulfilled') { + const qs = result.value; + + if (qs.status === 'completed') { + completedIds.push(sid); + } else if (qs.status === 'failed') { + completedIds.push(sid); + } else { + newProgress[sid] = { + progress: qs.progress || 0, + completedComponents: qs.completed_components || [], + }; + } + } else if (result.status === 'rejected') { + const err = result.reason; + // If cancelled, ignore; if 404, treat as completed (zombie session) + if (!axios.isCancel(err) && err?.response?.status === 404) { + completedIds.push(sid); + } + } + }); + + // Update progress for still-running items + setRunningProgress((prev) => ({ ...prev, ...newProgress })); + + // For completed items: fetch updated data and update in-place + if (completedIds.length > 0) { + const detailResults = await Promise.allSettled( + completedIds.map((sid) => getHistoryDetail(sid, userId || '')) + ); + + setItems((prev) => + prev.map((item) => { + const idx = completedIds.indexOf(item.session_id); + if (idx === -1) return item; + const detailResult = detailResults[idx]; + if (detailResult.status === 'fulfilled') { + return detailResult.value; + } + // If fetch failed, at least mark as completed + return { ...item, status: 'completed' }; + }) + ); + + // Remove completed items from progress tracking + setRunningProgress((prev) => { + const next = { ...prev }; + completedIds.forEach((sid) => delete next[sid]); + return next; + }); + } + }, [userId]); + + // Keep itemsRef in sync so interval callback always reads fresh items + useEffect(() => { + itemsRef.current = items; + }, [items]); + + // Start/stop polling based on running items + useEffect(() => { + const runningIds = getRunningIds(items); + + // Clean up progress for items no longer in the list + setRunningProgress((prev) => { + const next: Record = {}; + for (const sid of runningIds) { + if (prev[sid]) next[sid] = prev[sid]; + } + return next; + }); + + if (runningIds.length === 0 || !isFocusedRef.current) { + if (pollTimerRef.current) { + clearInterval(pollTimerRef.current); + pollTimerRef.current = null; + } + if (abortRef.current) { + abortRef.current.abort(); + abortRef.current = null; + } + pollStartTimeRef.current = 0; + return; + } + + // Start polling + if (!pollTimerRef.current) { + // Record when polling started for max duration check + if (pollStartTimeRef.current === 0) { + pollStartTimeRef.current = Date.now(); + } + pollTimerRef.current = setInterval(() => { + // Read from ref to avoid stale closure over `items` + const currentRunning = getRunningIds(itemsRef.current); + if (currentRunning.length > 0) { + pollRunningItems(currentRunning); + } + }, HISTORY_POLL_INTERVAL); + } + + return () => { + if (pollTimerRef.current) { + clearInterval(pollTimerRef.current); + pollTimerRef.current = null; + } + if (abortRef.current) { + abortRef.current.abort(); + abortRef.current = null; + } + }; + }, [items, getRunningIds, pollRunningItems]); + + useFocusEffect( + useCallback(() => { + isFocusedRef.current = true; + setLoading(true); + loadData(1, true).finally(() => setLoading(false)); + + return () => { + isFocusedRef.current = false; + if (pollTimerRef.current) { + clearInterval(pollTimerRef.current); + pollTimerRef.current = null; + } + if (abortRef.current) { + abortRef.current.abort(); + abortRef.current = null; + } + pollStartTimeRef.current = 0; + }; + }, [loadData]) + ); + + const handleRefresh = async () => { + setRefreshing(true); + await loadData(1, true); + setRefreshing(false); + }; + + const handleLoadMore = async () => { + if (loadingMore || page >= totalPages) return; + setLoadingMore(true); + await loadData(page + 1, false); + setLoadingMore(false); + }; + + const handlePress = async (item: AnalysisSession) => { + const isInProgress = item.status === 'running' || item.status === 'pending' || item.status === 'processing'; + + if (isInProgress) { + navigation.push('PipelineResults', { + sessionId: item.session_id, + components: item.components_run, + }); + return; + } + + try { + const detail = await getHistoryDetail(item.session_id, userId || ''); + navigation.push('PipelineResults', { result: detail }); + } catch (err: any) { + Alert.alert(t('error'), err?.message || t('failedToLoadDetails')); + } + }; + + const handleDelete = (item: AnalysisSession) => { + Alert.alert( + t('deleteAnalysis'), + t('deleteConfirm'), + [ + { text: t('cancel'), style: 'cancel' }, + { + text: t('delete'), + style: 'destructive', + onPress: async () => { + try { + await deleteHistoryItem(item.session_id, userId || ''); + setItems((prev) => prev.filter((i) => i.session_id !== item.session_id)); + } catch (err: any) { + Alert.alert(t('error'), err?.message || t('failedToDelete')); + } + }, + }, + ] + ); + }; + + const renderItem = ({ item }: { item: AnalysisSession }) => ( + handlePress(item)} + onLongPress={() => handleDelete(item)} + progress={runningProgress[item.session_id]} + /> + ); + + const renderFooter = () => { + if (!loadingMore) return null; + return ( + + ); + }; + + const renderEmpty = () => { + if (loading) return null; + return ( + + -- + {t('noAnalysesYet')} + {t('runFirstAnalysis')} + + ); + }; + + return ( + + + + + {/* Header */} + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + + + {t('history')} + + + {loading && items.length === 0 ? ( + + + + ) : ( + item.session_id} + renderItem={renderItem} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} + ListEmptyComponent={renderEmpty} + ListFooterComponent={renderFooter} + onEndReached={handleLoadMore} + onEndReachedThreshold={0.3} + refreshControl={ + + } + /> + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingTop: SPACING.xxl, + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.md, + }, + backButton: { + width: 40, + height: 40, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white5, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + alignSelf: 'center', + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + listContent: { + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.xxxl, + flexGrow: 1, + }, + centerLoader: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + emptyContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingTop: SPACING.xxxl * 2, + }, + emptyIcon: { + fontSize: 64, + marginBottom: SPACING.md, + opacity: 0.5, + }, + emptyTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.sm, + }, + emptyText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.base, + }, +}); diff --git a/android/didi-app-source/src/screens/PipelineResultsScreen.tsx b/android/didi-app-source/src/screens/PipelineResultsScreen.tsx new file mode 100644 index 0000000..c8777e2 --- /dev/null +++ b/android/didi-app-source/src/screens/PipelineResultsScreen.tsx @@ -0,0 +1,424 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { + View, + Text, + TouchableOpacity, + ScrollView, + StyleSheet, + StatusBar, + ActivityIndicator, + Alert, + AppState, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; +import { + COLORS, + GRADIENTS, + SPACING, + FONT_SIZES, + FONT_WEIGHTS, + RADIUS, +} from '../theme/colors'; +import type { RootStackParamList } from '../navigation/AppNavigator'; +import type { AnalysisSession } from '../types/analysis'; +import axios from 'axios'; +import { pollQueueStatus, getPipelineResult } from '../api/analysis'; +import { POLL_CONFIG } from '../api/config'; +import { + Verdict, TechniquesResults, AiResults, ClaimsResults, SourceResults, toAiDisplayResult, +} from '../components/sections'; +import { InputPreview } from '../components/editorial'; +import DidYouKnowCard from '../components/results/DidYouKnowCard'; +import { useTechniqueDefinitions } from '../hooks/useTechniqueDefinitions'; +import { notifyAnalysisComplete, requestNotificationPermissions } from '../utils/notifications'; +import { useTranslation } from '../i18n'; +import type { TranslationKeys } from '../i18n/en'; + +const COMPONENT_LABEL_KEYS: Record = { + techniques: { labelKey: 'manipulationTechniques', icon: 'MT' }, + claims: { labelKey: 'claimsVerification', icon: 'CV' }, + ai_tampered: { labelKey: 'aiDetection', icon: 'AI' }, + 'ai-tampered': { labelKey: 'aiDetection', icon: 'AI' }, + domain: { labelKey: 'sourceAssessment', icon: 'SA' }, + source_assessment: { labelKey: 'sourceAssessment', icon: 'SA' }, + context: { labelKey: 'contextAnalysis', icon: 'CA' }, +}; + +const ALL_COMPONENTS = ['techniques', 'claims', 'ai_tampered', 'domain']; + +type PipelineStatus = 'polling' | 'done' | 'error'; + +function normalizeComp(name: string): string { + if (name === 'ai-tampered') return 'ai_tampered'; + return name; +} + +export default function PipelineResultsScreen() { + const { t } = useTranslation(); + const navigation = useNavigation(); + const route = useRoute>(); + + const params = route.params as { + sessionId?: string; + components?: string[]; + result?: AnalysisSession; + }; + const isLive = !!params.sessionId; + + const expectedComponents = (params.components?.map(normalizeComp) || ALL_COMPONENTS); + + const [result, setResult] = useState(params.result || null); + const [status, setStatus] = useState(isLive ? 'polling' : 'done'); + const [progress, setProgress] = useState(isLive ? 0 : 100); + const [completedComponents, setCompletedComponents] = useState([]); + const [error, setError] = useState(null); + const { definitions, defMap } = useTechniqueDefinitions(); + + const pollTimerRef = useRef | null>(null); + const abortRef = useRef(null); + const prevCompletedCountRef = useRef(0); + const consecutiveErrorsRef = useRef(0); + const unmountedRef = useRef(false); + const appStateRef = useRef(AppState.currentState); + const notifiedRef = useRef(false); + + useEffect(() => { + if (isLive) requestNotificationPermissions(); + }, [isLive]); + + useEffect(() => { + const sub = AppState.addEventListener('change', (state) => { + appStateRef.current = state; + }); + return () => sub.remove(); + }, []); + + const paramsKey = params.sessionId || 'static'; + const prevParamsKeyRef = useRef(paramsKey); + useEffect(() => { + if (prevParamsKeyRef.current !== paramsKey) { + prevParamsKeyRef.current = paramsKey; + setResult(params.result || null); + setStatus(isLive ? 'polling' : 'done'); + setProgress(isLive ? 0 : 100); + setCompletedComponents([]); + setError(null); + prevCompletedCountRef.current = 0; + consecutiveErrorsRef.current = 0; + } + }, [paramsKey, isLive, params.result]); + + const cleanup = useCallback(() => { + if (pollTimerRef.current) { + clearTimeout(pollTimerRef.current); + pollTimerRef.current = null; + } + if (abortRef.current) { + abortRef.current.abort(); + abortRef.current = null; + } + }, []); + + // Polling logic — unchanged from original + useEffect(() => { + if (!isLive || !params.sessionId) return; + + unmountedRef.current = false; + const sessionId = params.sessionId; + const startTime = Date.now(); + + const doPoll = async () => { + if (unmountedRef.current) return; + + if (Date.now() - startTime > POLL_CONFIG.MAX_DURATION) { + if (prevCompletedCountRef.current > 0) { + try { + const partialResult = await getPipelineResult(sessionId); + if (!unmountedRef.current && partialResult) setResult(partialResult); + } catch { /* keep partial */ } + if (!unmountedRef.current) { + setProgress(100); + setStatus('done'); + } + } else { + setStatus('error'); + setError(t('analysisTimedOut')); + } + return; + } + + abortRef.current = new AbortController(); + const { signal } = abortRef.current; + + try { + const queueStatus = await pollQueueStatus(sessionId, signal); + if (unmountedRef.current) return; + + consecutiveErrorsRef.current = 0; + + const newProgress = Math.max(0, Math.min(100, queueStatus.progress || 0)); + setProgress(newProgress); + setCompletedComponents((queueStatus.completed_components || []).map(normalizeComp)); + + const newCount = (queueStatus.completed_components || []).length; + if (newCount > prevCompletedCountRef.current && queueStatus.session) { + prevCompletedCountRef.current = newCount; + setResult(queueStatus.session); + } + + if (queueStatus.status === 'completed') { + try { + const finalResult = await getPipelineResult(sessionId, signal); + if (!unmountedRef.current) setResult(finalResult); + } catch { /* keep partial */ } + if (!unmountedRef.current) { + setProgress(100); + setStatus('done'); + } + return; + } + + if (queueStatus.status === 'failed') { + try { + const partialResult = await getPipelineResult(sessionId, signal); + if (!unmountedRef.current && partialResult) setResult(partialResult); + } catch { /* none */ } + if (!unmountedRef.current) { + setProgress(100); + setStatus('done'); + } + return; + } + + pollTimerRef.current = setTimeout(doPoll, POLL_CONFIG.INTERVAL); + } catch (err: unknown) { + if (axios.isCancel(err)) return; + if (unmountedRef.current) return; + + const status404 = + typeof err === 'object' && err !== null && + 'response' in err && + (err as any).response?.status === 404; + + if (status404) { + setStatus('error'); + setError(t('sessionNotFound')); + return; + } + + consecutiveErrorsRef.current += 1; + if (consecutiveErrorsRef.current >= 5) { + if (prevCompletedCountRef.current > 0) { + setProgress(100); + setStatus('done'); + } else { + const isServerError = + typeof err === 'object' && err !== null && + 'response' in err && + (err as any).response?.status >= 500; + setStatus('error'); + setError(isServerError ? t('serverError') : t('connectionLost')); + } + return; + } + pollTimerRef.current = setTimeout(doPoll, POLL_CONFIG.INTERVAL); + } + }; + + doPoll(); + + return () => { + unmountedRef.current = true; + cleanup(); + }; + }, [isLive, params.sessionId, cleanup, t]); + + useEffect(() => { + if (status === 'done' && isLive && !notifiedRef.current) { + notifiedRef.current = true; + notifyAnalysisComplete({ + sessionId: params.sessionId || '', + riskScore: result?.risk_score ?? undefined, + riskCategory: result?.risk_category ?? undefined, + }); + } + }, [status, isLive, params.sessionId, result]); + + useEffect(() => { + if (status === 'error' && error) { + Alert.alert(t('analysisError'), error, [ + { text: t('goBack'), onPress: () => navigation.goBack() }, + ]); + } + }, [status, error, navigation, t]); + + const isPolling = status === 'polling'; + const componentsRun = result?.components_run || []; + const isSingleComponent = componentsRun.length === 1; + + const pollingStatusText = (() => { + if (!isPolling) return ''; + const total = expectedComponents.length; + const done = completedComponents.length; + if (done === 0) return t('processingMedia'); + if (done >= total - 1) return t('generatingVerdict'); + return t('processingProgress', { done, total }); + })(); + + return ( + + + + + + {/* Header */} + + navigation.goBack()} style={styles.backButton}> + + + + {t('analysisResults')} + {isPolling && {pollingStatusText}} + + + + {/* Progress card (live only) */} + {isPolling && ( + + + + + {t('percentComplete', { percent: Math.round(progress) })} + + {expectedComponents.map((comp) => { + const done = completedComponents.includes(comp); + const info = COMPONENT_LABEL_KEYS[comp] || { labelKey: comp as TranslationKeys, icon: '○' }; + return ( + + {done ? ( + + ) : ( + + )} + + {t(info.labelKey)} + + + ); + })} + + + )} + + {/* "Did you know" educational card while polling */} + {isPolling && definitions.length > 0 && ( + + )} + + {/* InputPreview shown once result lands */} + {!isPolling && result && ( + + )} + + {/* Pipeline (multi-component) — full Verdict, identical to web */} + {!isPolling && result?.verdict && !isSingleComponent && ( + + )} + + {/* Single-component sessions — render the matching shared section */} + {!isPolling && isSingleComponent && (() => { + if (result?.techniques) return ; + if (result?.ai_tampered) return ( + + ); + if (result?.claims) return ; + if (result?.source_assessment) return ; + return ( + + {t('noResults')} + + ); + })()} + + {/* Empty state */} + {!isPolling && !result?.verdict && !isSingleComponent && !result?.techniques + && !result?.claims && !result?.ai_tampered && !result?.source_assessment && !result?.domain && ( + + {t('noResults')} + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg.primary }, + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.md, + }, + backButton: { + width: 40, height: 40, borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white5, + justifyContent: 'center', alignItems: 'center', + marginRight: SPACING.md, alignSelf: 'center', + }, + backText: { color: COLORS.text.primary, fontSize: FONT_SIZES.xl }, + headerTitle: { color: COLORS.text.primary, fontSize: FONT_SIZES.xl, fontWeight: FONT_WEIGHTS.bold }, + headerSubtitle: { color: COLORS.text.secondary, fontSize: FONT_SIZES.sm, marginTop: 2 }, + scrollContent: { + paddingHorizontal: SPACING.lg, + paddingTop: SPACING.xxl, + paddingBottom: SPACING.xxxl, + }, + progressCard: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, padding: SPACING.md, marginBottom: SPACING.md, + }, + progressBarBg: { + width: '100%', height: 6, borderRadius: 3, + backgroundColor: COLORS.glass.white10, overflow: 'hidden', + marginBottom: SPACING.xs, + }, + progressBarFill: { height: '100%', borderRadius: 3, backgroundColor: COLORS.brand.primary }, + progressText: { color: COLORS.text.secondary, fontSize: FONT_SIZES.xs, marginBottom: SPACING.sm }, + chipContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: SPACING.xs }, + chip: { + flexDirection: 'row', alignItems: 'center', + paddingHorizontal: SPACING.sm, paddingVertical: SPACING.xs, + borderRadius: RADIUS.full, gap: 4, + }, + chipDone: { backgroundColor: 'rgba(40,167,69,0.15)' }, + chipPending: { backgroundColor: COLORS.glass.white5 }, + chipIconDone: { color: COLORS.status.green, fontSize: FONT_SIZES.xs, fontWeight: FONT_WEIGHTS.bold }, + chipLabel: { color: COLORS.text.muted, fontSize: FONT_SIZES.xs }, + chipLabelDone: { color: COLORS.status.green }, + emptyCard: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, padding: SPACING.xl, alignItems: 'center', + }, + emptyText: { color: COLORS.text.secondary, fontSize: FONT_SIZES.base }, +}); diff --git a/android/didi-app-source/src/screens/PipelineScreen.tsx b/android/didi-app-source/src/screens/PipelineScreen.tsx new file mode 100644 index 0000000..e78bd5b --- /dev/null +++ b/android/didi-app-source/src/screens/PipelineScreen.tsx @@ -0,0 +1,875 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + StatusBar, + ScrollView, + Alert, + Image, + KeyboardAvoidingView, + Platform, + ActivityIndicator, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import * as ImagePicker from 'expo-image-picker'; +import * as DocumentPicker from 'expo-document-picker'; +import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../theme/colors'; +import { useAuthStore } from '../store/authStore'; +import TabSelector from '../components/TabSelector'; +import UploadConsentModal from '../components/UploadConsentModal'; +import { submitPipelineAsync, uploadMedia } from '../api/analysis'; +import type { RootStackParamList } from '../navigation/AppNavigator'; +import { detectPlatform } from '../utils/shareIntent'; +import { loadSocialToggles } from '../utils/socialShareStorage'; +import { loadUploadConsent, saveUploadConsent } from '../utils/uploadConsent'; +import { + validateText, + validateUrl, + validateFile, + getMediaDuration, + validateMediaDuration, + type ValidationResult, +} from '../utils/validation'; +import type { MediaFile } from '../types/analysis'; +import { useTranslation } from '../i18n'; + +export default function PipelineScreen() { + const { t } = useTranslation(); + + const TABS = [ + { key: 'text', label: t('tabText') }, + { key: 'image', label: t('tabImage') }, + { key: 'audio', label: t('tabAudio') }, + { key: 'video', label: t('tabVideo') }, + { key: 'url', label: t('tabUrl') }, + ]; + const navigation = useNavigation>(); + const route = useRoute>(); + const { userId, userEmail } = useAuthStore(); + + const [activeTab, setActiveTab] = useState('text'); + const [textInput, setTextInput] = useState(''); + const [urlInput, setUrlInput] = useState(''); + const [mediaFile, setMediaFile] = useState(null); + const [mediaDuration, setMediaDuration] = useState(null); + const [inputFocused, setInputFocused] = useState(false); + const [inputError, setInputError] = useState(''); + const [inputWarning, setInputWarning] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [submitLabel, setSubmitLabel] = useState(''); + + const [autoAnalyzeUrl, setAutoAnalyzeUrl] = useState(null); + + // Explicit upload consent (persisted in AsyncStorage) + const [uploadConsent, setUploadConsent] = useState(false); + const [consentVisible, setConsentVisible] = useState(false); + + useEffect(() => { + loadUploadConsent().then(setUploadConsent); + }, []); + + // Handle shared URL from share sheet + useEffect(() => { + const sharedUrl = route.params?.sharedUrl; + if (!sharedUrl) return; + + setActiveTab('url'); + setUrlInput(sharedUrl); + + // Check if we should auto-analyze + (async () => { + try { + const platform = detectPlatform(sharedUrl); + if (platform === 'unknown') return; + const toggles = await loadSocialToggles(); + if (toggles[platform]) { + setAutoAnalyzeUrl(sharedUrl); + } + } catch { + // Social toggle check failed — user can still analyze manually + } + })(); + }, [route.params?.sharedUrl]); + + // Handle shared plain text (no URL) from share sheet + useEffect(() => { + const sharedText = route.params?.sharedText; + if (!sharedText) return; + + setActiveTab('text'); + setTextInput(sharedText); + applyValidation(validateText(sharedText)); + }, [route.params?.sharedText]); + + // Handle shared image from share sheet + useEffect(() => { + const sharedImage = route.params?.sharedImage; + if (!sharedImage) return; + + setActiveTab('image'); + trySetMediaFile({ + uri: sharedImage.uri, + name: sharedImage.fileName || 'shared-image.jpg', + type: sharedImage.mimeType || 'image/jpeg', + size: sharedImage.size, + }, 'image'); + }, [route.params?.sharedImage]); + + // Trigger auto-analyze after state has settled + useEffect(() => { + if (autoAnalyzeUrl && activeTab === 'url' && urlInput === autoAnalyzeUrl && !submitting) { + setAutoAnalyzeUrl(null); + handleAnalyze(); + } + }, [autoAnalyzeUrl, activeTab, urlInput]); + + const trySetMediaFile = async (file: MediaFile, category: 'image' | 'audio' | 'video') => { + const result = validateFile(file, category); + if (result.status === 'error') { + Alert.alert(t('invalidFile'), result.message); + return; + } + + // Duration validation for audio/video + if (category === 'audio' || category === 'video') { + const duration = await getMediaDuration(file.uri); + if (duration != null) { + const durationError = validateMediaDuration(duration, category); + if (durationError) { + Alert.alert(t('invalidFile'), durationError); + return; + } + setMediaDuration(duration); + } + } else { + setMediaDuration(null); + } + + setMediaFile(file); + }; + + const pickImage = async () => { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ['images'], + // Native crop UI: lets the user select a region before submitting. + // No `aspect` -> free-form crop on Android. + allowsEditing: true, + quality: 0.8, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.fileName || 'image.jpg', + type: asset.mimeType || 'image/jpeg', + size: asset.fileSize, + }, 'image'); + } + }; + + const takePhoto = async () => { + const permission = await ImagePicker.requestCameraPermissionsAsync(); + if (!permission.granted) { + Alert.alert(t('permissionRequired'), t('cameraPermission')); + return; + } + const result = await ImagePicker.launchCameraAsync({ + mediaTypes: ['images'], + // Native crop UI after capture (free aspect) + allowsEditing: true, + quality: 0.8, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.fileName || 'photo.jpg', + type: asset.mimeType || 'image/jpeg', + size: asset.fileSize, + }, 'image'); + } + }; + + const pickVideo = async () => { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ['videos'], + quality: 0.8, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.fileName || 'video.mp4', + type: asset.mimeType || 'video/mp4', + size: asset.fileSize, + }, 'video'); + } + }; + + const pickAudio = async () => { + const result = await DocumentPicker.getDocumentAsync({ + type: 'audio/*', + copyToCacheDirectory: true, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.name, + type: asset.mimeType || 'audio/mpeg', + size: asset.size, + }, 'audio'); + } + }; + + const applyValidation = (result: ValidationResult) => { + switch (result.status) { + case 'error': + setInputError(result.message); + setInputWarning(''); + break; + case 'warning': + setInputError(''); + setInputWarning(result.message); + break; + case 'valid': + case 'empty': + setInputError(''); + setInputWarning(''); + break; + // error_corrected handled at call site + } + }; + + const handleTextChange = (value: string) => { + setTextInput(value); + applyValidation(validateText(value)); + }; + + const handleUrlChange = (value: string) => { + const result = validateUrl(value); + if (result.status === 'error_corrected') { + setUrlInput(result.corrected); + setInputError(''); + setInputWarning(''); + } else { + setUrlInput(value); + applyValidation(result); + } + }; + + const handleAnalyze = async () => { + // Explicit consent required before the first media file upload + if (['image', 'audio', 'video'].includes(activeTab) && mediaFile && !uploadConsent) { + setConsentVisible(true); + return; + } + await runAnalysis(); + }; + + const handleConsentAccept = async () => { + setConsentVisible(false); + setUploadConsent(true); + try { + await saveUploadConsent(); + } catch { + // Persisting is best-effort; consent holds for this session + } + await runAnalysis(); + }; + + const runAnalysis = async () => { + setSubmitting(true); + + try { + let body: Record = { + user_id: userId, + plan_type: 1, + }; + + if (activeTab === 'text') { + body.text = textInput; + body.media_type = 'text'; + } else if (activeTab === 'url') { + body.url = urlInput; + body.media_type = 'url'; + } else { + body.media_type = activeTab; + if (mediaFile) { + setSubmitLabel(t('uploadingMedia')); + const publicUrl = await uploadMedia( + mediaFile.uri, + mediaFile.name, + mediaFile.type, + userId || '' + ); + body.media_url = publicUrl; + if (mediaDuration != null) { + body.media_duration_sec = Math.round(mediaDuration); + } + } + } + + setSubmitLabel(t('startingAnalysis')); + const response = await submitPipelineAsync(body); + + // Backend skipped extraction (private video, no audio, etc.) + const resData = response.data as any; + if (resData?.skipped) { + const platform = resData.platform_info?.displayName || ''; + const title = platform ? `${platform} — ${t('contentUnavailable')}` : t('contentUnavailable'); + const message = resData.user_message || resData.message || t('contentNotExtracted'); + const hint = resData.suggestion; + const body = hint ? `${message}\n\n${hint}` : message; + + Alert.alert( + title, + body, + [ + { text: t('cancel'), style: 'cancel' }, + { + text: t('uploadManually'), + onPress: () => { + setActiveTab('video'); + setMediaFile(null); + }, + }, + ], + ); + return; + } + + if (response.async && response.session_id) { + const components = 'queued_components' in response.data + ? (response.data as any).queued_components + : undefined; + navigation.push('PipelineResults', { sessionId: response.session_id, components }); + } else if (response.data) { + // Sync fallback: full result returned directly + navigation.push('PipelineResults', { result: response.data as any }); + } + } catch (err: any) { + const message = + err?.response?.data?.message || + err?.response?.data?.error || + err?.message || + t('failedToStartAnalysis'); + Alert.alert(t('analysisError'), message); + } finally { + setSubmitting(false); + setSubmitLabel(''); + } + }; + + const clearMedia = () => { setMediaFile(null); setMediaDuration(null); }; + + const canAnalyze = + !submitting && + !inputError && + ((activeTab === 'text' && textInput.trim().length > 0) || + (activeTab === 'url' && urlInput.trim().length > 0) || + (['image', 'audio', 'video'].includes(activeTab) && mediaFile !== null)); + + return ( + + + + + + + {/* Header */} + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + + + + {t('fullAnalysis')} + {t('runAllComponents')} + + + + {/* Tabs */} + { + setActiveTab(key); + setMediaFile(null); + setMediaDuration(null); + setInputError(''); + setInputWarning(''); + if (key !== 'text') setTextInput(''); + if (key !== 'url') setUrlInput(''); + }} /> + + {/* Input Area */} + + {activeTab === 'text' && ( + <> + {t('textToAnalyze')} + {t('enterTextHint')} + setInputFocused(true)} + onBlur={() => setInputFocused(false)} + /> + {textInput.length > 0 && ( + {textInput.length} {t('characters')} + )} + {!!inputError && {inputError}} + {!!inputWarning && {inputWarning}} + + )} + + {activeTab === 'url' && ( + <> + {t('urlToAnalyze')} + {t('enterUrlHint')} + setInputFocused(true)} + onBlur={() => setInputFocused(false)} + /> + {!!inputError && {inputError}} + + )} + + {activeTab === 'image' && ( + <> + {t('imageToAnalyze')} + {t('takePhotoOrSelect')} + {mediaFile ? ( + + + + + + {mediaFile.name} + + ) : ( + + + + + + + {t('gallery')} + + + + O + + {t('camera')} + + + )} + + )} + + {activeTab === 'audio' && ( + <> + {t('audioToAnalyze')} + {t('selectAudioHint')} + {mediaFile ? ( + + + A + + {mediaFile.name} + + + + + ) : ( + + + + + + {t('selectAudio')} + + )} + + )} + + {activeTab === 'video' && ( + <> + {t('videoToAnalyze')} + {t('selectVideoHint')} + {mediaFile ? ( + + + + + {mediaFile.name} + + + + + ) : ( + + + + + + {t('selectVideo')} + + )} + + )} + + + {/* Analyze Button */} + + + {submitting ? ( + + + + {submitLabel || t('submitting')} + + + ) : ( + + {t('runFullAnalysis')} + + )} + + + + {/* Discrete consent reminder on subsequent media uploads */} + {uploadConsent && ['image', 'audio', 'video'].includes(activeTab) && mediaFile && ( + {t('uploadConsentReminder')} + )} + + + + {/* Explicit upload consent (first media upload) */} + setConsentVisible(false)} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.sm, + }, + backButton: { + width: 40, + height: 40, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white5, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + alignSelf: 'center', + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + }, + headerTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + headerSubtitle: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + scrollContent: { + flexGrow: 1, + justifyContent: 'center', + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.lg, + }, + inputCard: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginTop: SPACING.md, + }, + inputLabel: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.xs, + }, + inputHint: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.sm, + marginBottom: SPACING.md, + }, + textInput: { + backgroundColor: COLORS.glass.white10, + borderWidth: 1, + borderColor: COLORS.border.secondary, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.sm + 6, + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + height: LAYOUT.INPUT_HEIGHT_LARGE, + textAlignVertical: 'top', + }, + textInputFocused: { + borderWidth: 2, + borderColor: COLORS.brand.primary, + }, + urlInput: { + backgroundColor: COLORS.glass.white10, + borderWidth: 1, + borderColor: COLORS.border.secondary, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.sm + 6, + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + height: LAYOUT.INPUT_HEIGHT_SMALL, + }, + charCount: { + color: COLORS.text.muted, + fontSize: FONT_SIZES.xs, + textAlign: 'right', + marginTop: SPACING.xs, + }, + imagePickerRow: { + flexDirection: 'row', + gap: SPACING.md, + }, + imagePickerOption: { + flex: 1, + backgroundColor: COLORS.bg.mediaOption, + borderWidth: 1, + borderColor: COLORS.border.media, + borderRadius: RADIUS.xl, + paddingVertical: SPACING.xl, + alignItems: 'center', + justifyContent: 'center', + }, + mediaPicker: { + backgroundColor: COLORS.bg.mediaOption, + borderWidth: 1, + borderColor: COLORS.border.media, + borderRadius: RADIUS.xl, + paddingVertical: SPACING.xxl, + alignItems: 'center', + justifyContent: 'center', + }, + mediaPickerIcon: { + width: 56, + height: 56, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.purple10, + justifyContent: 'center', + alignItems: 'center', + marginBottom: SPACING.sm, + }, + mediaPickerPlus: { + color: COLORS.brand.primary, + fontSize: FONT_SIZES.displaySm, + fontWeight: FONT_WEIGHTS.bold, + }, + mediaPickerText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + mediaPreview: { + borderRadius: RADIUS.xl, + overflow: 'hidden', + position: 'relative', + }, + imagePreview: { + width: '100%', + height: 200, + borderRadius: RADIUS.xl, + }, + removeMedia: { + position: 'absolute', + top: SPACING.sm, + right: SPACING.sm, + width: 32, + height: 32, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.black50, + justifyContent: 'center', + alignItems: 'center', + }, + removeMediaText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + mediaName: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.xs, + marginTop: SPACING.sm, + flex: 1, + }, + mediaPreviewFile: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: COLORS.bg.mediaOption, + borderWidth: 1, + borderColor: COLORS.border.media, + borderRadius: RADIUS.xl, + padding: SPACING.md, + }, + fileIcon: { + width: 44, + height: 44, + borderRadius: RADIUS.lg, + backgroundColor: COLORS.glass.purple10, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + fileIconText: { + color: COLORS.brand.primary, + fontSize: FONT_SIZES.xl, + }, + removeMediaInline: { + width: 32, + height: 32, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white10, + justifyContent: 'center', + alignItems: 'center', + marginLeft: SPACING.sm, + }, + analyzeButton: { + borderRadius: RADIUS.xl, + paddingVertical: SPACING.md, + alignItems: 'center', + justifyContent: 'center', + marginTop: SPACING.lg, + }, + analyzeButtonDisabled: { + opacity: 0.5, + }, + analyzeButtonText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + analyzeButtonTextDisabled: { + color: COLORS.text.muted, + }, + validationError: { + color: COLORS.status.red, + fontSize: FONT_SIZES.xs, + marginTop: SPACING.xs, + }, + validationWarning: { + color: COLORS.status.orange, + fontSize: FONT_SIZES.xs, + marginTop: SPACING.xs, + }, + consentReminder: { + color: COLORS.text.muted, + fontSize: FONT_SIZES.xs, + textAlign: 'center', + marginTop: SPACING.sm, + lineHeight: 16, + }, +}); diff --git a/android/didi-app-source/src/screens/SettingsScreen.tsx b/android/didi-app-source/src/screens/SettingsScreen.tsx new file mode 100644 index 0000000..e4cf5b0 --- /dev/null +++ b/android/didi-app-source/src/screens/SettingsScreen.tsx @@ -0,0 +1,203 @@ +import React, { useState, useCallback } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + StatusBar, + ScrollView, + RefreshControl, + ActivityIndicator, + Platform, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation, useFocusEffect } from '@react-navigation/native'; +import { COLORS, GRADIENTS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS } from '../theme/colors'; +import { useAuthStore } from '../store/authStore'; +import { fetchSubscriptionUsage, fetchCredits } from '../api/auth'; +import { loadSocialToggles, saveSocialToggles } from '../utils/socialShareStorage'; +import { useTranslation } from '../i18n'; +import type { SocialToggles } from '../utils/socialShareStorage'; + +import ProfileSection from '../components/settings/ProfileSection'; +import ChangePasswordSection from '../components/settings/ChangePasswordSection'; +import SubscriptionSection from '../components/settings/SubscriptionSection'; +import SocialShareSection from '../components/settings/SocialShareSection'; +import LanguageSection from '../components/settings/LanguageSection'; +import SignOutSection from '../components/settings/SignOutSection'; + +export default function SettingsScreen() { + const navigation = useNavigation(); + const { t } = useTranslation(); + const { profile } = useAuthStore(); + const [usageStats, setUsageStats] = useState(null); + const [creditsData, setCreditsData] = useState<{ credits_remained: number; credits_spent: number } | null>(null); + const [socialToggles, setSocialToggles] = useState({ + facebook: false, + x: false, + tiktok: false, + }); + const [refreshing, setRefreshing] = useState(false); + const [loading, setLoading] = useState(true); + + const loadData = useCallback(async () => { + try { + const [usage, credits, toggles] = await Promise.all([ + fetchSubscriptionUsage().catch(() => null), + fetchCredits().catch(() => null), + loadSocialToggles(), + ]); + if (usage) setUsageStats(usage); + if (credits) setCreditsData(credits); + setSocialToggles(toggles); + } finally { + setLoading(false); + } + }, []); + + useFocusEffect( + useCallback(() => { + loadData(); + }, [loadData]) + ); + + const handleRefresh = async () => { + setRefreshing(true); + await loadData(); + setRefreshing(false); + }; + + const handleToggle = async (platform: keyof SocialToggles) => { + const previous = { ...socialToggles }; + const updated = { ...socialToggles, [platform]: !socialToggles[platform] }; + setSocialToggles(updated); + try { + await saveSocialToggles(updated); + } catch { + // Rollback on save failure + setSocialToggles(previous); + } + }; + + if (loading) { + return ( + + + + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + + + {t('settings')} + + + + + + ); + } + + return ( + + + + + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + + + {t('settings')} + + + + } + > + + + + + {Platform.OS !== 'web' && ( + + )} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingTop: SPACING.xxl, + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.md, + }, + backButton: { + width: 40, + height: 40, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white5, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + alignSelf: 'center', + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + }, + title: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + center: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + scrollView: { + flex: 1, + }, + scrollContent: { + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.xxxl, + }, +}); diff --git a/android/didi-app-source/src/screens/ShareActionScreen.tsx b/android/didi-app-source/src/screens/ShareActionScreen.tsx new file mode 100644 index 0000000..948c725 --- /dev/null +++ b/android/didi-app-source/src/screens/ShareActionScreen.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import { View, Text, StyleSheet, StatusBar, ScrollView, Image } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, +} from '../theme/colors'; +import GlassCard from '../components/GlassCard'; +import type { RootStackParamList, SharedContentParams } from '../navigation/AppNavigator'; +import { useTranslation } from '../i18n'; + +export default function ShareActionScreen() { + const { t } = useTranslation(); + const navigation = useNavigation>(); + const route = useRoute>(); + const { sharedUrl, sharedText, sharedImage } = route.params; + + const kind: 'url' | 'text' | 'image' = sharedImage ? 'image' : sharedText ? 'text' : 'url'; + + const shareParams: SharedContentParams = + kind === 'image' ? { sharedImage } + : kind === 'text' ? { sharedText } + : { sharedUrl }; + + const headerTitle = + kind === 'image' ? t('analyzeImage') + : kind === 'text' ? t('analyzeText') + : t('analyzeUrl'); + + return ( + + + + + + {/* Header */} + + {headerTitle} + {t('chooseAnalysisType')} + + + {/* Shared content preview */} + + {kind === 'url' && ( + <> + {t('url')} + {sharedUrl} + + )} + {kind === 'text' && ( + <> + {t('sharedTextLabel')} + {sharedText} + + )} + {kind === 'image' && sharedImage && ( + <> + {t('sharedImageLabel')} + + {sharedImage.fileName} + + )} + + + {/* Options */} + navigation.replace('Pipeline', shareParams)} + /> + {kind === 'image' && ( + navigation.replace('SkillAnalysis', { skillType: 'ai_tamper', ...shareParams })} + /> + )} + navigation.replace('SkillAnalysis', { skillType: 'claims', ...shareParams })} + /> + navigation.replace('SkillAnalysis', { skillType: 'techniques', ...shareParams })} + /> + {kind === 'url' && ( + navigation.replace('SkillAnalysis', { skillType: 'source_assessment', ...shareParams })} + /> + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + scrollContent: { + flexGrow: 1, + justifyContent: 'center', + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.lg, + }, + header: { + marginBottom: SPACING.md, + }, + headerTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + headerSubtitle: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + previewCard: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: SPACING.lg, + marginBottom: SPACING.md, + }, + previewLabel: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.xs, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.xs, + }, + urlText: { + color: COLORS.brand.accentCyan, + fontSize: FONT_SIZES.sm, + }, + previewText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + lineHeight: 20, + }, + imagePreview: { + width: '100%', + height: 140, + borderRadius: RADIUS.lg, + marginBottom: SPACING.sm, + }, +}); diff --git a/android/didi-app-source/src/screens/SkillAnalysisScreen.tsx b/android/didi-app-source/src/screens/SkillAnalysisScreen.tsx new file mode 100644 index 0000000..dbef34a --- /dev/null +++ b/android/didi-app-source/src/screens/SkillAnalysisScreen.tsx @@ -0,0 +1,886 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + StatusBar, + ScrollView, + Alert, + Image, + KeyboardAvoidingView, + Platform, + ActivityIndicator, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import * as ImagePicker from 'expo-image-picker'; +import * as DocumentPicker from 'expo-document-picker'; +import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + COLORS, + GRADIENTS, + SPACING, + RADIUS, + FONT_SIZES, + FONT_WEIGHTS, + LAYOUT, +} from '../theme/colors'; +import { useAuthStore } from '../store/authStore'; +import TabSelector from '../components/TabSelector'; +import UploadConsentModal from '../components/UploadConsentModal'; +import { submitSkillAsync, uploadMedia } from '../api/analysis'; +import type { RootStackParamList } from '../navigation/AppNavigator'; +import { loadUploadConsent, saveUploadConsent } from '../utils/uploadConsent'; +import { + validateText, + validateUrl, + validateFile, + getMediaDuration, + validateMediaDuration, + type ValidationResult, +} from '../utils/validation'; +import type { MediaFile } from '../types/analysis'; +import { useTranslation } from '../i18n'; +import type { TranslationKeys } from '../i18n/en'; + +const SKILL_CONFIG: Record = { + techniques: { + titleKey: 'manipulationTechniques', + subtitleKey: 'detectManipulation', + tabKeys: ['text', 'image', 'audio', 'video', 'url'], + component: 'techniques', + }, + source_assessment: { + titleKey: 'sourceAssessment', + subtitleKey: 'evaluateSource', + tabKeys: ['text', 'image', 'audio', 'video', 'url'], + component: 'source-assessment', + }, + ai_tamper: { + titleKey: 'aiTamperDetection', + subtitleKey: 'detectAiContent', + tabKeys: ['text', 'image', 'audio', 'video'], + component: 'ai-tampered', + }, + claims: { + titleKey: 'claimsAnalysis', + subtitleKey: 'verifyClaims', + tabKeys: ['text', 'image', 'audio', 'video', 'url'], + component: 'claims', + }, +}; + +const TAB_LABEL_KEYS: Record = { + text: 'tabText', + image: 'tabImage', + audio: 'tabAudio', + video: 'tabVideo', + url: 'tabUrl', +}; + +export default function SkillAnalysisScreen() { + const navigation = useNavigation>(); + const route = useRoute>(); + const { userId } = useAuthStore(); + const { t } = useTranslation(); + + const skillType = route.params.skillType; + const sharedUrl = route.params.sharedUrl; + const sharedText = route.params.sharedText; + const sharedImage = route.params.sharedImage; + const config = SKILL_CONFIG[skillType]; + + const tabs = config.tabKeys.map((key) => ({ + key, + label: t(TAB_LABEL_KEYS[key]), + })); + + // Determine initial tab based on shared content and available tabs + const hasUrlTab = config.tabKeys.includes('url'); + const hasImageTab = config.tabKeys.includes('image'); + const initialTab = + sharedImage && hasImageTab ? 'image' + : sharedUrl && hasUrlTab ? 'url' + : sharedText ? 'text' + : config.tabKeys[0]; + + const [activeTab, setActiveTab] = useState(initialTab); + const [textInput, setTextInput] = useState(sharedUrl || sharedText || ''); + const [mediaFile, setMediaFile] = useState(null); + const [mediaDuration, setMediaDuration] = useState(null); + const [inputFocused, setInputFocused] = useState(false); + const [inputError, setInputError] = useState(''); + const [inputWarning, setInputWarning] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [submitLabel, setSubmitLabel] = useState(''); + + // Explicit upload consent (persisted in AsyncStorage) + const [uploadConsent, setUploadConsent] = useState(false); + const [consentVisible, setConsentVisible] = useState(false); + + useEffect(() => { + loadUploadConsent().then(setUploadConsent); + }, []); + + // Preload shared image from share sheet + useEffect(() => { + if (!sharedImage || !hasImageTab) return; + trySetMediaFile({ + uri: sharedImage.uri, + name: sharedImage.fileName || 'shared-image.jpg', + type: sharedImage.mimeType || 'image/jpeg', + size: sharedImage.size, + }, 'image'); + }, [sharedImage]); + + const trySetMediaFile = async (file: MediaFile, category: 'image' | 'audio' | 'video') => { + const result = validateFile(file, category); + if (result.status === 'error') { + Alert.alert(t('invalidFile'), result.message); + return; + } + + if (category === 'audio' || category === 'video') { + const duration = await getMediaDuration(file.uri); + if (duration != null) { + const durationError = validateMediaDuration(duration, category); + if (durationError) { + Alert.alert(t('invalidFile'), durationError); + return; + } + setMediaDuration(duration); + } + } else { + setMediaDuration(null); + } + + setMediaFile(file); + }; + + const pickImage = async () => { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ['images'], + // Native crop UI: lets the user select a region before submitting. + // No `aspect` -> free-form crop on Android. + allowsEditing: true, + quality: 0.8, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.fileName || 'image.jpg', + type: asset.mimeType || 'image/jpeg', + size: asset.fileSize, + }, 'image'); + } + }; + + const takePhoto = async () => { + const permission = await ImagePicker.requestCameraPermissionsAsync(); + if (!permission.granted) { + Alert.alert(t('permissionRequired'), t('cameraPermission')); + return; + } + const result = await ImagePicker.launchCameraAsync({ + mediaTypes: ['images'], + // Native crop UI after capture (free aspect) + allowsEditing: true, + quality: 0.8, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.fileName || 'photo.jpg', + type: asset.mimeType || 'image/jpeg', + size: asset.fileSize, + }, 'image'); + } + }; + + const pickVideo = async () => { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ['videos'], + quality: 0.8, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.fileName || 'video.mp4', + type: asset.mimeType || 'video/mp4', + size: asset.fileSize, + }, 'video'); + } + }; + + const pickAudio = async () => { + const result = await DocumentPicker.getDocumentAsync({ + type: 'audio/*', + copyToCacheDirectory: true, + }); + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + trySetMediaFile({ + uri: asset.uri, + name: asset.name, + type: asset.mimeType || 'audio/mpeg', + size: asset.size, + }, 'audio'); + } + }; + + const applyValidation = (result: ValidationResult) => { + switch (result.status) { + case 'error': + setInputError(result.message); + setInputWarning(''); + break; + case 'warning': + setInputError(''); + setInputWarning(result.message); + break; + case 'valid': + case 'empty': + setInputError(''); + setInputWarning(''); + break; + } + }; + + const handleTextInputChange = (value: string) => { + setTextInput(value); + + if (activeTab === 'url') { + const result = validateUrl(value); + if (result.status === 'error_corrected') { + setTextInput(result.corrected); + setInputError(''); + setInputWarning(''); + } else { + applyValidation(result); + } + } else { + applyValidation(validateText(value)); + } + }; + + const handleAnalyze = async () => { + // Explicit consent required before the first media file upload + if (['image', 'audio', 'video'].includes(activeTab) && mediaFile && !uploadConsent) { + setConsentVisible(true); + return; + } + await runAnalysis(); + }; + + const handleConsentAccept = async () => { + setConsentVisible(false); + setUploadConsent(true); + try { + await saveUploadConsent(); + } catch { + // Persisting is best-effort; consent holds for this session + } + await runAnalysis(); + }; + + const runAnalysis = async () => { + setSubmitting(true); + + try { + let body: Record = { + user_id: userId, + }; + + if (activeTab === 'text') { + body.text = textInput; + } else if (activeTab === 'url') { + body.url = textInput.trim(); + } else { + body.media_type = activeTab; + if (mediaFile) { + setSubmitLabel(t('uploadingMedia')); + const publicUrl = await uploadMedia( + mediaFile.uri, + mediaFile.name, + mediaFile.type, + userId || '' + ); + body.media_url = publicUrl; + if (mediaDuration != null) { + body.media_duration_sec = Math.round(mediaDuration); + } + } + } + + setSubmitLabel(t('startingAnalysis')); + const response = await submitSkillAsync(config.component, body); + + // Backend skipped extraction (private video, no audio, etc.) + const resData = response.data as any; + if (resData?.skipped) { + const platform = resData.platform_info?.displayName || ''; + const title = platform ? `${platform} — ${t('contentUnavailable')}` : t('contentUnavailable'); + const message = resData.user_message || resData.message || t('contentNotExtracted'); + const hint = resData.suggestion; + const alertBody = hint ? `${message}\n\n${hint}` : message; + + // Pick a sensible fallback tab for manual upload + const mediaTabs = config.tabKeys.filter((k) => + ['image', 'audio', 'video'].includes(k), + ); + const fallbackTab = mediaTabs.length > 0 ? mediaTabs[0] : 'text'; + + Alert.alert( + title, + alertBody, + [ + { text: t('cancel'), style: 'cancel' }, + { + text: t('uploadManually'), + onPress: () => { + setActiveTab(fallbackTab); + setMediaFile(null); + setInputError(''); + setInputWarning(''); + }, + }, + ], + ); + return; + } + + if (response.async && response.session_id) { + const components = 'queued_components' in response.data + ? (response.data as any).queued_components + : [config.component]; + navigation.push('PipelineResults', { sessionId: response.session_id, components }); + } else if (response.data) { + navigation.push('PipelineResults', { result: response.data as any }); + } + } catch (err: any) { + const message = + err?.response?.data?.message || + err?.response?.data?.error || + err?.message || + t('failedToStartAnalysis'); + Alert.alert(t('analysisError'), message); + } finally { + setSubmitting(false); + setSubmitLabel(''); + } + }; + + const clearMedia = () => { setMediaFile(null); setMediaDuration(null); }; + + const canAnalyze = + !submitting && + !inputError && + ((activeTab === 'text' && textInput.trim().length > 0) || + (activeTab === 'url' && textInput.trim().length > 0) || + (['image', 'audio', 'video'].includes(activeTab) && mediaFile !== null)); + + const isUrlTab = activeTab === 'url'; + + return ( + + + + + + + {/* Header */} + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + + + + {t(config.titleKey)} + {t(config.subtitleKey)} + + + {/* Tabs - only show if more than 1 */} + {tabs.length > 1 && ( + { + setActiveTab(key); + setMediaFile(null); + setMediaDuration(null); + setInputError(''); + setInputWarning(''); + setTextInput(''); + }} + /> + )} + + {/* Input Area */} + + {(activeTab === 'text' || isUrlTab) && ( + <> + + {isUrlTab ? t('urlToAnalyze') : t('textToAnalyze')} + + + {isUrlTab + ? t('enterUrlHint') + : t('enterTextToAnalyze')} + + {isUrlTab ? ( + <> + setInputFocused(true)} + onBlur={() => setInputFocused(false)} + /> + {!!inputError && {inputError}} + + ) : ( + <> + setInputFocused(true)} + onBlur={() => setInputFocused(false)} + /> + {textInput.length > 0 && ( + {textInput.length} {t('characters')} + )} + {!!inputError && {inputError}} + {!!inputWarning && {inputWarning}} + + )} + + )} + + {activeTab === 'image' && ( + <> + {t('imageToAnalyze')} + {t('takePhotoOrSelect')} + {mediaFile ? ( + + + + + + {mediaFile.name} + + ) : ( + + + + + + + {t('gallery')} + + + + O + + {t('camera')} + + + )} + + )} + + {activeTab === 'audio' && ( + <> + {t('audioToAnalyze')} + {t('selectAudioHint')} + {mediaFile ? ( + + + A + + {mediaFile.name} + + + + + ) : ( + + + + + + {t('selectAudio')} + + )} + + )} + + {activeTab === 'video' && ( + <> + {t('videoToAnalyze')} + {t('selectVideoHint')} + {mediaFile ? ( + + + + + {mediaFile.name} + + + + + ) : ( + + + + + + {t('selectVideo')} + + )} + + )} + + + {/* Analyze Button */} + + + {submitting ? ( + + + + {submitLabel || t('submitting')} + + + ) : ( + + {t('analyze')} + + )} + + + + {/* Discrete consent reminder on subsequent media uploads */} + {uploadConsent && ['image', 'audio', 'video'].includes(activeTab) && mediaFile && ( + {t('uploadConsentReminder')} + )} + + + + {/* Explicit upload consent (first media upload) */} + setConsentVisible(false)} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.sm, + }, + backButton: { + width: 40, + height: 40, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white5, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + alignSelf: 'center', + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + }, + headerTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + headerSubtitle: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + scrollContent: { + flexGrow: 1, + justifyContent: 'center', + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.lg, + }, + inputCard: { + backgroundColor: COLORS.bg.card, + borderWidth: 1, + borderColor: COLORS.border.accent, + borderRadius: RADIUS.xxl, + padding: LAYOUT.CARD_PADDING, + marginTop: SPACING.md, + }, + inputLabel: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + marginBottom: SPACING.xs, + }, + inputHint: { + color: COLORS.text.hint, + fontSize: FONT_SIZES.sm, + marginBottom: SPACING.md, + }, + textInput: { + backgroundColor: COLORS.glass.white10, + borderWidth: 1, + borderColor: COLORS.border.secondary, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.sm + 6, + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + height: LAYOUT.INPUT_HEIGHT_LARGE, + textAlignVertical: 'top', + }, + urlInput: { + backgroundColor: COLORS.glass.white10, + borderWidth: 1, + borderColor: COLORS.border.secondary, + borderRadius: RADIUS.lg, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.sm + 6, + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + height: LAYOUT.INPUT_HEIGHT_SMALL, + }, + inputFocused: { + borderWidth: 2, + borderColor: COLORS.brand.primary, + }, + charCount: { + color: COLORS.text.muted, + fontSize: FONT_SIZES.xs, + textAlign: 'right', + marginTop: SPACING.xs, + }, + mediaPicker: { + backgroundColor: COLORS.bg.mediaOption, + borderWidth: 1, + borderColor: COLORS.border.media, + borderRadius: RADIUS.xl, + paddingVertical: SPACING.xl + 8, + alignItems: 'center', + justifyContent: 'center', + }, + imagePickerRow: { + flexDirection: 'row', + gap: SPACING.md, + }, + imagePickerOption: { + flex: 1, + backgroundColor: COLORS.bg.mediaOption, + borderWidth: 1, + borderColor: COLORS.border.media, + borderRadius: RADIUS.xl, + paddingVertical: SPACING.xl, + alignItems: 'center', + justifyContent: 'center', + }, + mediaPickerIcon: { + width: 56, + height: 56, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.purple10, + justifyContent: 'center', + alignItems: 'center', + marginBottom: SPACING.sm, + }, + mediaPickerPlus: { + color: COLORS.brand.primary, + fontSize: FONT_SIZES.displaySm, + fontWeight: FONT_WEIGHTS.bold, + }, + mediaPickerText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + fontWeight: FONT_WEIGHTS.bold, + }, + mediaPreview: { + borderRadius: RADIUS.xl, + overflow: 'hidden', + }, + imagePreview: { + width: '100%', + height: 200, + borderRadius: RADIUS.xl, + }, + removeMedia: { + position: 'absolute', + top: SPACING.sm, + right: SPACING.sm, + width: 32, + height: 32, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.black50, + justifyContent: 'center', + alignItems: 'center', + }, + removeMediaText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + mediaName: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.xs, + marginTop: SPACING.sm, + flex: 1, + }, + mediaPreviewFile: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: COLORS.bg.mediaOption, + borderWidth: 1, + borderColor: COLORS.border.media, + borderRadius: RADIUS.xl, + padding: SPACING.md, + }, + fileIcon: { + width: 44, + height: 44, + borderRadius: RADIUS.lg, + backgroundColor: COLORS.glass.purple10, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + }, + fileIconText: { + color: COLORS.brand.primary, + fontSize: FONT_SIZES.xl, + }, + removeMediaInline: { + width: 32, + height: 32, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white10, + justifyContent: 'center', + alignItems: 'center', + marginLeft: SPACING.sm, + }, + analyzeButton: { + borderRadius: RADIUS.xl, + paddingVertical: SPACING.md, + alignItems: 'center', + justifyContent: 'center', + marginTop: SPACING.lg, + }, + analyzeButtonDisabled: { + opacity: 0.5, + }, + analyzeButtonText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + analyzeButtonTextDisabled: { + color: COLORS.text.muted, + }, + validationError: { + color: COLORS.status.red, + fontSize: FONT_SIZES.xs, + marginTop: SPACING.xs, + }, + validationWarning: { + color: COLORS.status.orange, + fontSize: FONT_SIZES.xs, + marginTop: SPACING.xs, + }, + consentReminder: { + color: COLORS.text.muted, + fontSize: FONT_SIZES.xs, + textAlign: 'center', + marginTop: SPACING.sm, + lineHeight: 16, + }, +}); diff --git a/android/didi-app-source/src/screens/SkillsScreen.tsx b/android/didi-app-source/src/screens/SkillsScreen.tsx new file mode 100644 index 0000000..5733c44 --- /dev/null +++ b/android/didi-app-source/src/screens/SkillsScreen.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, ScrollView, StyleSheet, StatusBar } from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { COLORS, GRADIENTS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS, LAYOUT } from '../theme/colors'; +import GlassCard from '../components/GlassCard'; +import type { RootStackParamList } from '../navigation/AppNavigator'; +import { useTranslation } from '../i18n'; + +export default function SkillsScreen() { + const { t } = useTranslation(); + const navigation = useNavigation>(); + + return ( + + + + + + {/* Header */} + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + + + + {t('skills')} + {t('analyzeIndividualAspects')} + + + + navigation.navigate('SkillAnalysis', { skillType: 'techniques' })} + /> + navigation.navigate('SkillAnalysis', { skillType: 'source_assessment' })} + /> + navigation.navigate('SkillAnalysis', { skillType: 'ai_tamper' })} + /> + navigation.navigate('SkillAnalysis', { skillType: 'claims' })} + /> + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: SPACING.sm, + }, + backButton: { + width: 40, + height: 40, + borderRadius: RADIUS.full, + backgroundColor: COLORS.glass.white5, + justifyContent: 'center', + alignItems: 'center', + marginRight: SPACING.md, + alignSelf: 'center', + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + }, + headerTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.xl, + fontWeight: FONT_WEIGHTS.bold, + }, + headerSubtitle: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: 2, + }, + scrollContent: { + flexGrow: 1, + justifyContent: 'center', + paddingHorizontal: SPACING.lg, + paddingVertical: SPACING.lg, + }, +}); diff --git a/android/didi-app-source/src/screens/auth/LoginScreen.tsx b/android/didi-app-source/src/screens/auth/LoginScreen.tsx new file mode 100644 index 0000000..1122332 --- /dev/null +++ b/android/didi-app-source/src/screens/auth/LoginScreen.tsx @@ -0,0 +1,303 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ActivityIndicator, + Alert, + StatusBar, + Platform, +} from 'react-native'; +import * as Crypto from 'expo-crypto'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { COLORS, SPACING, FONT_SIZES, FONT_WEIGHTS } from '../../theme/colors'; +import { AUTH_CONFIG, getRedirectUri } from '../../api/config'; +import { exchangeCodeForTokens, fetchProfile } from '../../api/auth'; +import { useAuthStore } from '../../store/authStore'; +import { useTranslation } from '../../i18n'; +import { loadPendingShare, clearPendingShare } from '../../utils/socialShareStorage'; +import { sharedPayloadToParams } from '../../utils/shareIntent'; +import type { RootStackParamList } from '../../navigation/AppNavigator'; + +// Conditional import: WebView only on native +const WebView = Platform.OS !== 'web' + ? require('react-native-webview').WebView + : null; + +interface LoginScreenProps { + isRegister?: boolean; +} + +// Generate PKCE code verifier (43 chars, base64url) using crypto-safe randomness +function generateCodeVerifier(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; + const randomBytes = Crypto.getRandomBytes(43); + let result = ''; + for (let i = 0; i < 43; i++) { + result += chars[randomBytes[i] % chars.length]; + } + return result; +} + +// Generate random state for CSRF protection +function generateState(): string { + const bytes = Crypto.getRandomBytes(24); + return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); +} + +export default function LoginScreen({ isRegister = false }: LoginScreenProps) { + const { t } = useTranslation(); + const navigation = useNavigation>(); + const { setTokens, setProfile } = useAuthStore(); + const [loading, setLoading] = useState(false); + const [webviewLoading, setWebviewLoading] = useState(true); + const [codeVerifier] = useState(generateCodeVerifier); + const [codeChallenge, setCodeChallenge] = useState(null); + const [oauthState] = useState(generateState); + const webviewRef = useRef(null); + const handledRedirectRef = useRef(false); + + // Generate code challenge from verifier + useEffect(() => { + (async () => { + const digest = await Crypto.digestStringAsync( + Crypto.CryptoDigestAlgorithm.SHA256, + codeVerifier, + { encoding: Crypto.CryptoEncoding.BASE64 } + ); + // Convert base64 to base64url + const challenge = digest + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + setCodeChallenge(challenge); + })(); + }, [codeVerifier]); + + const redirectUri = getRedirectUri(); + + const authUrl = codeChallenge + ? `${isRegister ? AUTH_CONFIG.REGISTER_URL : AUTH_CONFIG.AUTH_URL}` + + `?client_id=${AUTH_CONFIG.CLIENT_ID}` + + `&redirect_uri=${encodeURIComponent(redirectUri)}` + + `&response_type=code` + + `&scope=${encodeURIComponent(AUTH_CONFIG.SCOPES)}` + + `&code_challenge=${codeChallenge}` + + `&code_challenge_method=S256` + + `&state=${oauthState}` + : null; + + // Web: redirect browser directly to Keycloak + useEffect(() => { + if (Platform.OS === 'web' && authUrl) { + // Persist PKCE verifier + state for callback + sessionStorage.setItem('pkce_code_verifier', codeVerifier); + sessionStorage.setItem('oauth_state', oauthState); + // replace() instead of href: Keycloak URL must not enter browser history, + // otherwise Back from Dashboard returns to /auth/callback (stale code). + window.location.replace(authUrl); + } + }, [authUrl]); + + const handleNavigationChange = async (navState: { url: string }) => { + const { url } = navState; + + // Intercept redirect to callback URL + if (url.startsWith(redirectUri)) { + // Prevent double-fire from onNavigationStateChange + onShouldStartLoadWithRequest + if (handledRedirectRef.current) return; + handledRedirectRef.current = true; + + // Check for error response from auth server + const errorMatch = url.match(/[?&]error=([^&]+)/); + if (errorMatch) { + Alert.alert(t('authError'), t('authDenied')); + handledRedirectRef.current = false; + return; + } + + // Validate state parameter (CSRF protection) + const stateMatch = url.match(/[?&]state=([^&]+)/); + if (!stateMatch || stateMatch[1] !== oauthState) { + Alert.alert(t('securityError'), t('invalidAuthResponse')); + handledRedirectRef.current = false; + return; + } + + const codeMatch = url.match(/[?&]code=([^&]+)/); + if (!codeMatch) { + Alert.alert(t('error'), t('noAuthCode')); + handledRedirectRef.current = false; + return; + } + + const authCode = codeMatch[1]; + setLoading(true); + + try { + const tokens = await exchangeCodeForTokens(authCode, codeVerifier); + await setTokens(tokens); + + // Sync profile with backend + try { + const profile = await fetchProfile(); + setProfile(profile); + } catch { + // Profile sync can fail silently + } + + // Check for pending shared content (URL/text/image) received before auth + const pendingShare = await loadPendingShare(); + if (pendingShare) { + await clearPendingShare(); + navigation.reset({ + index: 1, + routes: [ + { name: 'Dashboard' }, + { name: 'ShareAction', params: sharedPayloadToParams(pendingShare) }, + ], + }); + } else { + navigation.reset({ index: 0, routes: [{ name: 'Dashboard' }] }); + } + } catch { + Alert.alert( + t('authFailed'), + t('authFailedMessage') + ); + setLoading(false); + handledRedirectRef.current = false; + } + } + }; + + if (loading) { + return ( + + + + {t('authenticating')} + + ); + } + + return ( + + + + {/* Header */} + + navigation.goBack()} + style={styles.backButton} + accessibilityRole="button" + accessibilityLabel={t('back')} + > + ← {t('back')} + + + {isRegister ? t('createAccount') : t('signIn')} + + + + {/* Auth form */} + {Platform.OS === 'web' ? ( + + + {t('redirectingToLogin')} + + ) : authUrl && WebView ? ( + + {webviewLoading && ( + + + + )} + { + if (request.url.startsWith(redirectUri)) { + handleNavigationChange({ url: request.url }); + return false; + } + return true; + }} + onLoadStart={() => setWebviewLoading(true)} + onLoadEnd={() => setWebviewLoading(false)} + style={styles.webview} + originWhitelist={['https://', 'http://', 'didi://']} + javaScriptEnabled + domStorageEnabled + startInLoadingState={false} + incognito + /> + + ) : ( + + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + center: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + paddingTop: SPACING.xxl, + paddingHorizontal: SPACING.lg, + paddingBottom: SPACING.md, + backgroundColor: COLORS.bg.primary, + }, + backButton: { + paddingVertical: SPACING.sm, + paddingRight: SPACING.md, + }, + backText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + }, + headerTitle: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.lg, + fontWeight: FONT_WEIGHTS.bold, + }, + webviewContainer: { + flex: 1, + }, + webview: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + webviewLoading: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + backgroundColor: COLORS.bg.primary, + }, + loadingText: { + color: COLORS.text.secondary, + fontSize: FONT_SIZES.sm, + marginTop: SPACING.md, + }, +}); diff --git a/android/didi-app-source/src/screens/auth/MainScreen.tsx b/android/didi-app-source/src/screens/auth/MainScreen.tsx new file mode 100644 index 0000000..7a55d2c --- /dev/null +++ b/android/didi-app-source/src/screens/auth/MainScreen.tsx @@ -0,0 +1,214 @@ +import React, { useEffect, useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + ActivityIndicator, + StatusBar, + Platform, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { COLORS, GRADIENTS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS } from '../../theme/colors'; +import { useAuthStore } from '../../store/authStore'; +import { useTranslation } from '../../i18n'; +import { exchangeCodeForTokens, fetchProfile } from '../../api/auth'; +import { loadPendingShare, clearPendingShare } from '../../utils/socialShareStorage'; +import { sharedPayloadToParams } from '../../utils/shareIntent'; +import type { RootStackParamList } from '../../navigation/AppNavigator'; + +export default function MainScreen() { + const { t } = useTranslation(); + const navigation = useNavigation>(); + const { isLoading, isAuthenticated, loadTokens, setTokens, setProfile } = useAuthStore(); + const [webAuthProcessing, setWebAuthProcessing] = useState(false); + + useEffect(() => { + // Web: check if this is an OAuth callback redirect + if (Platform.OS === 'web') { + const params = new URLSearchParams(window.location.search); + const code = params.get('code'); + const state = params.get('state'); + + if (code && state) { + const savedState = sessionStorage.getItem('oauth_state'); + const codeVerifier = sessionStorage.getItem('pkce_code_verifier'); + + // Clean URL immediately + window.history.replaceState({}, '', '/'); + + if (state === savedState && codeVerifier) { + const isPasswordChange = sessionStorage.getItem('password_change') === 'true'; + sessionStorage.removeItem('oauth_state'); + sessionStorage.removeItem('pkce_code_verifier'); + sessionStorage.removeItem('password_change'); + + setWebAuthProcessing(true); + (async () => { + try { + const tokens = await exchangeCodeForTokens(code, codeVerifier); + await setTokens(tokens); + try { + const profile = await fetchProfile(); + setProfile(profile); + } catch {} + navigation.reset({ index: 0, routes: [{ name: 'Dashboard' }] }); + } catch { + setWebAuthProcessing(false); + // Fall through to normal MainScreen + } + })(); + return; + } + } + } + loadTokens(); + }, []); + + useEffect(() => { + if (!isLoading && isAuthenticated) { + (async () => { + const pendingShare = await loadPendingShare(); + if (pendingShare) { + await clearPendingShare(); + navigation.reset({ + index: 1, + routes: [ + { name: 'Dashboard' }, + { name: 'ShareAction', params: sharedPayloadToParams(pendingShare) }, + ], + }); + } else { + navigation.reset({ index: 0, routes: [{ name: 'Dashboard' }] }); + } + })(); + } + }, [isLoading, isAuthenticated]); + + if (isLoading || webAuthProcessing) { + return ( + + + + + + ); + } + + return ( + + + + + + {/* Logo */} + + {t('appName')} + {t('appSubtitle')} + + + {/* Buttons */} + + navigation.navigate('Login')} + accessibilityRole="button" + accessibilityLabel={t('signIn')} + > + + {t('signIn')} + + + + navigation.navigate('Register')} + accessibilityRole="button" + accessibilityLabel={t('createAccount')} + > + {t('createAccount')} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.bg.primary, + }, + center: { + justifyContent: 'center', + alignItems: 'center', + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: SPACING.lg, + }, + logoContainer: { + alignItems: 'center', + marginBottom: SPACING.xxxl, + }, + logo: { + fontSize: FONT_SIZES.displayLg, + fontWeight: FONT_WEIGHTS.bold, + color: COLORS.text.primary, + letterSpacing: 2, + }, + subtitle: { + fontSize: FONT_SIZES.base, + color: COLORS.text.secondary, + marginTop: SPACING.sm, + }, + buttonsContainer: { + width: '100%', + gap: SPACING.md, + }, + primaryButton: { + borderRadius: RADIUS.xl, + paddingVertical: SPACING.md, + alignItems: 'center', + }, + primaryButtonText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, + secondaryButton: { + backgroundColor: COLORS.glass.white5, + borderWidth: 1, + borderColor: COLORS.glass.purple10, + borderRadius: RADIUS.xl, + paddingVertical: SPACING.md, + alignItems: 'center', + }, + secondaryButtonText: { + color: COLORS.text.primary, + fontSize: FONT_SIZES.base, + fontWeight: FONT_WEIGHTS.bold, + }, +}); diff --git a/android/didi-app-source/src/screens/auth/RegisterScreen.tsx b/android/didi-app-source/src/screens/auth/RegisterScreen.tsx new file mode 100644 index 0000000..e380f6a --- /dev/null +++ b/android/didi-app-source/src/screens/auth/RegisterScreen.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import LoginScreen from './LoginScreen'; + +export default function RegisterScreen() { + return ; +} diff --git a/android/didi-app-source/src/store/authStore.ts b/android/didi-app-source/src/store/authStore.ts new file mode 100644 index 0000000..d627036 --- /dev/null +++ b/android/didi-app-source/src/store/authStore.ts @@ -0,0 +1,170 @@ +import { create } from 'zustand'; +import { Platform } from 'react-native'; +import * as SecureStore from 'expo-secure-store'; +import axios from 'axios'; +import { AuthState, TokenResponse, UserProfile } from '../types/auth'; +import { AUTH_CONFIG } from '../api/config'; + +// Web fallback: localStorage instead of SecureStore +const storage = { + getItem: (key: string): Promise => { + if (Platform.OS === 'web') return Promise.resolve(localStorage.getItem(key)); + return SecureStore.getItemAsync(key); + }, + setItem: (key: string, value: string): Promise => { + if (Platform.OS === 'web') { localStorage.setItem(key, value); return Promise.resolve(); } + return SecureStore.setItemAsync(key, value); + }, + deleteItem: (key: string): Promise => { + if (Platform.OS === 'web') { localStorage.removeItem(key); return Promise.resolve(); } + return SecureStore.deleteItemAsync(key); + }, +}; +import { getUserIdFromToken, getEmailFromToken } from '../utils/jwt'; + +// Mutex: only one token refresh at a time +let refreshPromise: Promise | null = null; + +export const useAuthStore = create((set, get) => ({ + accessToken: null, + refreshToken: null, + expiresAt: null, + userId: null, + userEmail: null, + profile: null, + isAuthenticated: false, + isLoading: true, + + setTokens: async (tokens: TokenResponse) => { + const expiresAt = Date.now() + tokens.expires_in * 1000; + const userId = getUserIdFromToken(tokens.access_token); + const userEmail = getEmailFromToken(tokens.access_token); + + await storage.setItem('access_token', tokens.access_token); + await storage.setItem('refresh_token', tokens.refresh_token); + await storage.setItem('expires_at', expiresAt.toString()); + + set({ + accessToken: tokens.access_token, + refreshToken: tokens.refresh_token, + expiresAt, + userId, + userEmail, + isAuthenticated: true, + isLoading: false, + }); + }, + + loadTokens: async () => { + try { + const accessToken = await storage.getItem('access_token'); + const refreshToken = await storage.getItem('refresh_token'); + const expiresAtStr = await storage.getItem('expires_at'); + + if (!accessToken || !refreshToken) { + set({ isLoading: false, isAuthenticated: false }); + return false; + } + + const expiresAt = Number(expiresAtStr); + const userId = getUserIdFromToken(accessToken); + const userEmail = getEmailFromToken(accessToken); + + // If token expired, try refresh + if (Date.now() > expiresAt) { + const newToken = await get().refreshAccessToken(); + if (!newToken) { + set({ isLoading: false, isAuthenticated: false }); + return false; + } + return true; + } + + set({ + accessToken, + refreshToken, + expiresAt, + userId, + userEmail, + isAuthenticated: true, + isLoading: false, + }); + return true; + } catch { + set({ isLoading: false, isAuthenticated: false }); + return false; + } + }, + + clearTokens: async () => { + // Revoke refresh token on server before clearing locally + try { + const { refreshToken } = get(); + if (refreshToken) { + const params = new URLSearchParams({ + client_id: AUTH_CONFIG.CLIENT_ID, + refresh_token: refreshToken, + }); + await axios.post(AUTH_CONFIG.LOGOUT_URL, params.toString(), { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + timeout: 5000, + }).catch(() => {}); // Best effort — don't block logout on server error + } + } catch { + // Server revocation is best-effort + } + + await storage.deleteItem('access_token'); + await storage.deleteItem('refresh_token'); + await storage.deleteItem('expires_at'); + + set({ + accessToken: null, + refreshToken: null, + expiresAt: null, + userId: null, + userEmail: null, + profile: null, + isAuthenticated: false, + isLoading: false, + }); + }, + + setProfile: (profile: UserProfile) => { + set({ profile }); + }, + + refreshAccessToken: async () => { + // If a refresh is already in-flight, wait for it instead of firing another + if (refreshPromise) return refreshPromise; + + refreshPromise = (async (): Promise => { + try { + const { refreshToken } = get(); + if (!refreshToken) return null; + + const params = new URLSearchParams({ + grant_type: 'refresh_token', + client_id: AUTH_CONFIG.CLIENT_ID, + refresh_token: refreshToken, + }); + + const response = await axios.post(AUTH_CONFIG.TOKEN_URL, params.toString(), { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + timeout: 30000, + }); + + const tokens: TokenResponse = response.data; + await get().setTokens(tokens); + return tokens.access_token; + } catch { + await get().clearTokens(); + return null; + } + })().finally(() => { + refreshPromise = null; + }); + + return refreshPromise; + }, +})); diff --git a/android/didi-app-source/src/theme/colors.ts b/android/didi-app-source/src/theme/colors.ts new file mode 100644 index 0000000..0fbf1ac --- /dev/null +++ b/android/didi-app-source/src/theme/colors.ts @@ -0,0 +1,388 @@ +export const COLORS = { + // Brand + brand: { + primary: '#009198', + primaryDark: '#007177', + primaryLight: '#7fd0d4', + accentMagenta: '#1fb6bd', + accentCyan: '#7fd0d4', + cyanLight: '#e6f4f5', + }, + + // Backgrounds + bg: { + primary: '#0d1424', + secondary: '#111a2e', + surface: '#121c30', + card: '#111a2e', + cardElevated: '#1c2740', + dialog: '#162032', + mediaOption: '#131f33', + mediaPressed: '#1a2740', + }, + + // Text + text: { + primary: '#FFFFFF', + secondary: '#b9c2d0', + hint: '#8a94a6', + muted: '#6b7686', + darkMuted: '#5b6678', + }, + + // Borders + border: { + primary: '#1c2740', + secondary: '#28374f', + accent: '#1d3a44', + muted: '#263349', + media: '#1a2740', + }, + + // Status / Semantic + status: { + green: '#28A745', + lightGreen: '#90EE90', + yellow: '#FFD700', + orange: '#FF8C42', + red: '#E63946', + darkRed: '#8B0000', + cyan: '#7fd0d4', + purple: '#7fd0d4', + }, + + // Verdict + verdict: { + factual: '#1fb6bd', + questionable: '#FF8C42', + misinfo: '#E63946', + satire: '#7fd0d4', + }, + + // Glass / Transparency + glass: { + white5: 'rgba(255,255,255,0.05)', + white10: 'rgba(255,255,255,0.10)', + white15: 'rgba(255,255,255,0.15)', + white20: 'rgba(255,255,255,0.20)', + white30: 'rgba(255,255,255,0.30)', + purple10: 'rgba(0,145,152,0.10)', + purple25: 'rgba(0,145,152,0.25)', + purple35: 'rgba(0,145,152,0.35)', + cyan10: 'rgba(31,182,189,0.10)', + cyan20: 'rgba(31,182,189,0.20)', + black40: 'rgba(0,0,0,0.40)', + black50: 'rgba(0,0,0,0.50)', + black90: 'rgba(0,0,0,0.90)', + bg95: 'rgba(13,20,36,0.95)', + }, + + // Claim Status + claimStatus: { + VT: '#28A745', + VF: '#E63946', + LF: '#FF8C42', + LT: '#90EE90', + PV: '#FFD700', + UV: '#8a94a6', + NV: '#6b7686', + OP: '#7fd0d4', + } as Record, + + // Social + social: { + facebookStart: '#1877F2', + facebookEnd: '#0D65D9', + tiktokStart: '#FE2C55', + tiktokEnd: '#25F4EE', + twitterStart: '#2D2D2D', + twitterEnd: '#000000', + }, + + // Icon circles + iconCircles: { + purpleStart: '#1fb6bd', + purpleEnd: '#009198', + orangeStart: '#F97316', + orangeEnd: '#EA580C', + tealStart: '#1fb6bd', + tealEnd: '#009198', + light: '#F0F0F0', + }, + + // Recording + recording: { + stopStart: '#DC2626', + stopEnd: '#EF4444', + }, +}; + +export const GRADIENTS = { + button: { + colors: ['#009198', '#1fb6bd'] as const, + start: { x: 0, y: 1 }, + end: { x: 1, y: 0 }, + }, + avatar: { + colors: ['#009198', '#1fb6bd'] as const, + start: { x: 0, y: 0 }, + end: { x: 1, y: 1 }, + }, + bgOverlay: { + colors: ['rgba(13,20,36,0)', 'rgba(17,26,46,0.25)', 'rgba(0,145,152,0.4)'] as const, + start: { x: 0, y: 0 }, + end: { x: 1, y: 1 }, + locations: [0, 0.5, 1] as const, + }, + stop: { + colors: ['#DC2626', '#EF4444'] as const, + start: { x: 0, y: 0 }, + end: { x: 1, y: 0 }, + }, +}; + +export const SPACING = { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + xxl: 48, + xxxl: 64, +}; + +export const LAYOUT = { + CARD_PADDING: 20, + DIALOG_PADDING: 32, + SCROLL_BOTTOM: 120, + INPUT_HEIGHT_LARGE: 180, + INPUT_HEIGHT_SMALL: 52, + AVATAR_SIZE: 48, + AVATAR_SIZE_LARGE: 56, + ICON_CIRCLE_SIZE: 48, + SCORE_CIRCLE_SIZE: 120, +}; + +export const RADIUS = { + sm: 4, + md: 8, + lg: 12, + xl: 16, + xxl: 20, + xxxl: 24, + full: 9999, +}; + +export const FONT_SIZES = { + xs: 12, + sm: 14, + base: 16, + lg: 18, + xl: 20, + xxl: 24, + displaySm: 28, + displayMd: 32, + displayLg: 48, + displayXl: 56, +}; + +export const FONT_WEIGHTS = { + regular: '400' as const, + medium: '500' as const, + semibold: '600' as const, + bold: '700' as const, +}; + +export const FONTS = { + /** Default sans-serif (system). */ + sans: undefined as string | undefined, + /** Merriweather serif loaded by App.tsx via expo-font. Used for TL;DR and quote pull-strings. */ + serif: 'Merriweather-Regular', + serifItalic: 'Merriweather-Italic', + serifBold: 'Merriweather-Bold', +}; + +/** Editorial typography tokens — match web styles. */ +export const EDITORIAL = { + // Eyebrow above category names (10.5–11px uppercase, wide letterspacing) + eyebrow: { + fontSize: 11, + letterSpacing: 1.7, + fontWeight: FONT_WEIGHTS.semibold, + color: 'rgba(255,255,255,0.45)', + textTransform: 'uppercase' as const, + }, + // Headline category (24px sans, semi-bold, tight letter-spacing) + category: { + fontSize: 22, + fontWeight: FONT_WEIGHTS.semibold, + letterSpacing: -0.4, + color: '#FFFFFF', + }, + // Descriptor next to category (13px, dim, with separator dots) + descriptor: { + fontSize: 13, + color: 'rgba(255,255,255,0.65)', + }, + // TL;DR — Merriweather italic 17–18px serif, max 60ch + tldr: { + fontFamily: FONTS.serifItalic, + fontSize: 17, + lineHeight: 26, + color: 'rgba(255,255,255,0.85)', + }, + // Finding card headline (15.5px, medium) + findingHeadline: { + fontSize: 15.5, + lineHeight: 23, + fontWeight: FONT_WEIGHTS.medium, + color: 'rgba(255,255,255,0.92)', + letterSpacing: -0.15, + }, + findingHeadlineDominant: { + fontSize: 16.5, + lineHeight: 25, + fontWeight: FONT_WEIGHTS.medium, + color: 'rgba(255,255,255,0.92)', + letterSpacing: -0.15, + }, + // Pull-quote inside finding card — Merriweather italic with left rule + quote: { + fontFamily: FONTS.serifItalic, + fontSize: 14, + lineHeight: 22, + color: 'rgba(255,255,255,0.7)', + }, + // Source row (footer row inside FindingCard) + source: { + fontSize: 12.5, + color: 'rgba(255,255,255,0.45)', + letterSpacing: -0.05, + }, + // Section divider label + dividerLabel: { + fontSize: 11, + fontWeight: FONT_WEIGHTS.semibold, + letterSpacing: 1.9, + color: 'rgba(255,255,255,0.45)', + textTransform: 'uppercase' as const, + }, + dividerCount: { + fontSize: 11, + fontWeight: FONT_WEIGHTS.medium, + color: 'rgba(255,255,255,0.28)', + }, +}; + +/** Accent palette used by editorial finding cards. */ +export const ACCENT = { + critical: '#ef4444', + warning: '#f97316', + info: '#3b82f6', + success: '#22c55e', + neutral: '#94a3b8', + violet: '#a78bfa', +}; + +export type FindingAccent = keyof typeof ACCENT; + +export const LINE_HEIGHTS = { + tight: 16, + normal: 18, + relaxed: 20, + loose: 22, +}; + +export const SHADOWS = { + card: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 4, + }, + dialog: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, +}; + +// Score color functions +export function getScoreColorInverted(percent: number): string { + if (percent <= 30) return COLORS.status.green; + if (percent <= 60) return COLORS.status.orange; + return COLORS.status.red; +} + +export function getScoreColorDirect(percent: number): string { + if (percent >= 70) return COLORS.status.green; + if (percent >= 40) return COLORS.status.orange; + return COLORS.status.red; +} + +export function getRiskColor(colorName: string): string { + const map: Record = { + green: COLORS.status.green, + lightgreen: COLORS.status.lightGreen, + yellow: COLORS.status.yellow, + orange: COLORS.status.orange, + red: COLORS.status.red, + darkred: COLORS.status.darkRed, + }; + return map[colorName.toLowerCase()] || COLORS.text.primary; +} + +export function getBadgeBgColor(color: string, opacity: number = 0.15): string { + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + return `rgba(${r},${g},${b},${opacity})`; +} + +// Dimension names +export const DIMENSION_NAMES: Record = { + D1: 'Content & Packaging', + D2: 'Source & Attribution', + D3: 'Media & Synthetic', + D4: 'Emotional & Psychological', + D5: 'Social & Network', + D6: 'Platform & Technical', + D7: 'Temporal & Context', + D8: 'Targeting & Vulnerability', +}; + +// Claim labels +export const CLAIM_TYPE_LABELS: Record = { + FC: 'Factual', + CC: 'Causal', + PC: 'Predictive', + QA: 'Quote', + SC: 'Statistical', + OP: 'Opinion', + OF: 'Opinion as Fact', + HC: 'Historical', + AT: 'Attribution', +}; + +export const CLAIM_STATUS_LABELS: Record = { + VT: 'Verified True', + VF: 'Verified False', + LF: 'Likely False', + LT: 'Likely True', + PV: 'Partially Verified', + UV: 'Unverified', + NV: 'Not Verifiable', + OP: 'Opinion', +}; + +export const TYPE_ICONS: Record = { + text: 'T', + image: 'I', + audio: 'A', + video: 'V', + url: 'U', + default: 'U', +}; diff --git a/android/didi-app-source/src/types/analysis.ts b/android/didi-app-source/src/types/analysis.ts new file mode 100644 index 0000000..d81a999 --- /dev/null +++ b/android/didi-app-source/src/types/analysis.ts @@ -0,0 +1,348 @@ +// === Media File (used by picker screens) === +export interface MediaFile { + uri: string; + name: string; + type: string; + size?: number; +} + +// === Upload === +export interface UploadResponse { + success: boolean; + data: { + public_url: string; + object_key: string; + filename: string; + size: number; + content_type: string; + }; +} + +// === Verdict === +export interface V3Verdict { + risk_score: number; + risk_category: string; + risk_category_color: string | null; + risk_level: string; + risk_level_color: string | null; + severity: string | null; + recommended_action: string | null; + confidence: number; + confidence_level: 'HIGH' | 'MEDIUM' | 'LOW'; + score_manipulation: number | null; + score_claims: number | null; + score_ai: number | null; + score_source: number | null; + score_context: number | null; + applied_weights: Record; + override_applied: boolean; + override_type: string | null; + override_reason: string | null; + override_adjustment: number | null; + context_summary: Record; + components_used: string[]; + weights_source: string | null; + duration_ms: number | null; + explanation_ro: string | null; + explanation_en: string | null; + + // Virality (may be null if pipeline didn't run fully) + virality_score: number | null; + virality_level: 'LOW' | 'MODERATE' | 'HIGH' | 'VIRAL' | null; + virality_factors: V3ViralityFactor[] | null; +} + +export interface V3ViralityFactor { + factor: string; + score: number; + weight: number; + detail: string; +} + +// === Technique Definitions (from /techniques/definitions) === +export interface TechniqueDefinition { + technique_id: number; + technique_name: string; + dimension: string; + dimension_name: string; + subdimension: string; + severity: number; + description_en: string; + description_ro: string; + // Bilingual fields (backend may add _ro/_en variants) + [key: string]: any; +} + +// === Techniques === +export interface V3Technique { + technique_id: number; + confidence: number; + intensity: number; // 1-3 + evidence: string; + // May be enriched by backend: + name?: string; + severity?: number; + dimension?: string; + subdimension?: string; + // Bilingual fields (backend may add _ro/_en variants) + [key: string]: any; +} + +export interface V3TechniquesResult { + manipulation_score: number; + total_severity: number; + dimensions_affected: string[]; + techniques_count: number; + techniques_detected: V3Technique[]; + coupling_context: Record; + llm_screening: string | null; + llm_deep: string | null; + screening_duration_ms: number | null; + deep_analysis_duration_ms: number | null; + total_duration_ms: number | null; + fallbacks_screening: number; + fallbacks_deep: number; + summary?: string; +} + +// === Claims === +export interface V3ClaimSource { + url: string; + stance: string; + relevant_quote: string; +} + +export interface V3Claim { + id: string; + text: string; + type: string; + status: string; + confidence: number; + reasoning: string; + sources: V3ClaimSource[]; + // May be enriched by backend: + status_name?: string; + status_color?: string; +} + +export interface V3ClaimsResult { + total_claims: number; + verified_true: number; + verified_false: number; + unverified: number; + opinions: number; + credibility_score: number | null; + interpretation: string | null; + claims_by_status: Record; + claims_by_type: Record; + claims_verified: V3Claim[]; + llm_extraction: string | null; + llm_verification: string | null; + extraction_duration_ms: number | null; + verification_duration_ms: number | null; + total_duration_ms: number | null; + web_searches_made: number; +} + +// === AI Tampered === +export interface V3AiIndicator { + id: string; + category: string; + name: string; + confidence: number; + evidence: string; +} + +export interface V3AiTamperedResult { + ai_probability: number; + verdict: string; + risk_score: number; + categories_affected: string[]; + indicators_count: number; + disclosure_detected: boolean; + disclosure_explicit: boolean; + disclosure_text: string | null; + indicators_detected: V3AiIndicator[]; + coupling_context: Record; + llm_screening: string | null; + llm_deep: string | null; + screening_duration_ms: number | null; + deep_analysis_duration_ms: number | null; + total_duration_ms: number | null; + fallbacks_screening: number; + fallbacks_deep: number; + content_type: string; + image_analysis: Record | null; + summary?: string; +} + +// === Domain === +export interface V3DomainResult { + domain: string; + verdict: string; + trust_score: number; + risk_level: string | null; + age_days: number | null; + age_category: string | null; + domain_created_at: string | null; + is_blacklisted: boolean; + reputation_score: number | null; + has_ssl: boolean | null; + ssl_valid: boolean | null; + ssl_issuer: string | null; + registrar: string | null; + organization: string | null; + country: string | null; + red_flags: string[]; + warnings: string[]; + duration_ms: number | null; + summary?: string; +} + +// === Source Assessment (replaces Domain) === +export interface V3SourceAssessmentPublication { + name: string | null; + source_type: string; + source_type_id: number; + score: number; + confirmed: boolean; +} + +export interface V3SourceAssessmentAuthor { + name: string | null; + classification: string; + classification_code: string; + score: number; + confirmed: boolean; + credibility_indicators: string[]; +} + +export interface V3SourceAssessmentPlatform { + code: string; + name: string; + score: number; + modifiers: string[]; +} + +export interface V3SourceAssessmentDomain { + name: string | null; + age_days: number | null; + risk_score: number | null; + score: number; + has_ssl: boolean | null; + is_blacklisted: boolean; + registrar: string | null; + organization: string | null; + country: string | null; + red_flags: string[]; +} + +export interface V3SourceAssessmentFormula { + publication_weight: number; + domain_weight: number; + author_weight: number; + platform_weight: number; + breakdown: string; +} + +export interface V3SourceAssessmentResult { + trust_score: number; + verdict: string; + risk_level: string; + publication: V3SourceAssessmentPublication; + author: V3SourceAssessmentAuthor; + platform: V3SourceAssessmentPlatform; + domain: V3SourceAssessmentDomain; + formula: V3SourceAssessmentFormula; + warnings: string[]; + red_flags: string[]; + search_queries_used: string[]; + search_results_count: number; + duration_ms: number; + llm_model_used: string; +} + +// === Analysis Session (unified result) === +export interface AnalysisSession { + session_id: string; + user_id: string | null; + user_email: string | null; + input_type?: string; + input_text?: string; + input_url?: string | null; + input_media_url?: string | null; + input_hash?: string | null; + + status?: string; + components_run?: string[]; + components_skipped?: string[]; + + risk_score?: number | null; + risk_category?: string | null; + risk_level?: string | null; + confidence?: number | null; + confidence_level?: string | null; + + started_at?: string; + completed_at?: string | null; + total_duration_ms?: number | null; + created_at?: string; + + scenario_applied?: string | null; + topic_applied?: string | null; + source_app?: string; + api_version?: string; + + techniques?: V3TechniquesResult | null; + ai_tampered?: V3AiTamperedResult | null; + claims?: V3ClaimsResult | null; + domain?: V3DomainResult | null; + source_assessment?: V3SourceAssessmentResult | null; + verdict?: V3Verdict | null; + + // Queue info (present in queue-status responses) + _queue?: { + progress: number; + total_components: number; + completed_components: string[]; + elapsed_ms: number; + }; +} + +// === Queue Status (extracted from polling response) === +export interface V3QueueStatus { + session: AnalysisSession; + progress: number; + total_components: number; + completed_components: string[]; + status: string; +} + +// === V3 API Responses === +export interface V3Response { + success: boolean; + async?: boolean; + message?: string; + data: T; +} + +export interface V3AsyncSubmitData { + session_id: string; + status: string; + queued_components: string[]; + poll_url: string; + result_url: string; +} + +export interface V3HistoryResponse { + success: boolean; + data: { + items: AnalysisSession[]; + pagination: { + page: number; + limit: number; + total: number; + pages: number; + }; + }; +} diff --git a/android/didi-app-source/src/types/auth.ts b/android/didi-app-source/src/types/auth.ts new file mode 100644 index 0000000..e0dce3e --- /dev/null +++ b/android/didi-app-source/src/types/auth.ts @@ -0,0 +1,40 @@ +export interface TokenResponse { + access_token: string; + refresh_token: string; + expires_in: number; + refresh_expires_in: number; + token_type: string; +} + +export interface UserProfile { + personId?: number; + internetUserId?: number; + email: string; + firstName: string; + lastName: string; + phone?: string; + creditsRemained: number; + creditsSpent?: number; + subscriptionPlanId?: number; + subscriptionPlanName?: string; + subscriptionStatus?: number; + isActive?: boolean; + keycloakId?: string; +} + +export interface AuthState { + accessToken: string | null; + refreshToken: string | null; + expiresAt: number | null; + userId: string | null; + userEmail: string | null; + profile: UserProfile | null; + isAuthenticated: boolean; + isLoading: boolean; + + setTokens: (tokens: TokenResponse) => Promise; + loadTokens: () => Promise; + clearTokens: () => Promise; + setProfile: (profile: UserProfile) => void; + refreshAccessToken: () => Promise; +} diff --git a/android/didi-app-source/src/types/subscription.ts b/android/didi-app-source/src/types/subscription.ts new file mode 100644 index 0000000..2383d7a --- /dev/null +++ b/android/didi-app-source/src/types/subscription.ts @@ -0,0 +1,25 @@ +export interface UsageStats { + plan: { + name: string; + priceAmount: number; + creditsPerCycle: number; + maxImages: number; + maxVideoMinutes: number; + storageLimitGb: number; + }; + subscription: { + isActive: boolean; + }; + credits: { + remaining: number; + total: number; + spent: number; + }; + costs: { + text: number; + url: number; + image: number; + audio: number; + video: number; + }; +} diff --git a/android/didi-app-source/src/utils/formatTechniqueName.ts b/android/didi-app-source/src/utils/formatTechniqueName.ts new file mode 100644 index 0000000..e880b50 --- /dev/null +++ b/android/didi-app-source/src/utils/formatTechniqueName.ts @@ -0,0 +1,13 @@ +/** + * Format a technique name for display. + * "content_type.fabricated_content" → "Fabricated Content" + * Takes only the part after the last dot, replaces _ with space, capitalizes each word. + */ +export function formatTechniqueName(name: string): string { + const lastDot = name.lastIndexOf('.'); + const raw = lastDot >= 0 ? name.substring(lastDot + 1) : name; + return raw + .split('_') + .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) + .join(' '); +} diff --git a/android/didi-app-source/src/utils/jwt.ts b/android/didi-app-source/src/utils/jwt.ts new file mode 100644 index 0000000..caa5264 --- /dev/null +++ b/android/didi-app-source/src/utils/jwt.ts @@ -0,0 +1,22 @@ +export function decodeJwtPayload(token: string): Record | null { + try { + const parts = token.split('.'); + if (parts.length !== 3) return null; + + const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/'); + const decoded = atob(base64); + return JSON.parse(decoded); + } catch { + return null; + } +} + +export function getUserIdFromToken(token: string): string { + const payload = decodeJwtPayload(token); + return (payload?.sub as string) || ''; +} + +export function getEmailFromToken(token: string): string { + const payload = decodeJwtPayload(token); + return (payload?.email as string) || ''; +} diff --git a/android/didi-app-source/src/utils/notifications.ts b/android/didi-app-source/src/utils/notifications.ts new file mode 100644 index 0000000..f130df8 --- /dev/null +++ b/android/didi-app-source/src/utils/notifications.ts @@ -0,0 +1,48 @@ +import * as Notifications from 'expo-notifications'; +import { Platform } from 'react-native'; +import { getTranslation } from '../i18n'; + +// Configure how notifications appear when app is in foreground +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: false, + shouldShowBanner: true, + shouldShowList: true, + }), +}); + +/** Request notification permissions. Returns true if granted. */ +export async function requestNotificationPermissions(): Promise { + if (Platform.OS === 'web') return false; + const { status: existing } = await Notifications.getPermissionsAsync(); + if (existing === 'granted') return true; + const { status } = await Notifications.requestPermissionsAsync(); + return status === 'granted'; +} + +/** Show a local notification that analysis is complete. */ +export async function notifyAnalysisComplete(opts: { + sessionId: string; + riskScore?: number | null; + riskCategory?: string | null; +}): Promise { + if (Platform.OS === 'web') return; + const { sessionId, riskScore, riskCategory } = opts; + const { t } = getTranslation(); + + const body = riskScore != null && riskCategory + ? t('notifRiskScore', { score: riskScore, category: riskCategory }) + : t('tapToViewResults'); + + await Notifications.scheduleNotificationAsync({ + content: { + title: t('analysisComplete'), + body, + data: { sessionId }, + sound: true, + }, + trigger: null, // Immediately + }); +} diff --git a/android/didi-app-source/src/utils/shareIntent.ts b/android/didi-app-source/src/utils/shareIntent.ts new file mode 100644 index 0000000..3899332 --- /dev/null +++ b/android/didi-app-source/src/utils/shareIntent.ts @@ -0,0 +1,96 @@ +import type { ShareIntent } from 'expo-share-intent'; + +const URL_REGEX = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/i; + +export function extractUrlFromSharedText(text: string | undefined | null): string | null { + if (!text) return null; + const match = text.match(URL_REGEX); + return match ? match[0] : null; +} + +export type SocialPlatform = 'facebook' | 'x' | 'tiktok' | 'unknown'; + +export function detectPlatform(url: string): SocialPlatform { + const lower = url.toLowerCase(); + if (lower.includes('facebook.com') || lower.includes('fb.com') || lower.includes('fb.watch')) { + return 'facebook'; + } + if (lower.includes('twitter.com') || lower.includes('x.com') || lower.includes('t.co')) { + return 'x'; + } + if (lower.includes('tiktok.com')) { + return 'tiktok'; + } + return 'unknown'; +} + +/** Image received via the OS share sheet. */ +export interface SharedImage { + uri: string; + fileName: string; + mimeType: string; + size?: number; +} + +/** Normalized payload extracted from a native share intent. */ +export type SharedPayload = + | { kind: 'url'; url: string } + | { kind: 'text'; text: string } + | { kind: 'image'; image: SharedImage }; + +/** Convert a SharedPayload into navigation params for ShareAction/Pipeline/SkillAnalysis. */ +export function sharedPayloadToParams(payload: SharedPayload): { + sharedUrl?: string; + sharedText?: string; + sharedImage?: SharedImage; +} { + switch (payload.kind) { + case 'url': + return { sharedUrl: payload.url }; + case 'text': + return { sharedText: payload.text }; + case 'image': + return { sharedImage: payload.image }; + } +} + +/** + * Normalize an expo-share-intent ShareIntent into a payload the app can act on: + * - weburl / text containing a URL -> { kind: 'url' } + * - plain text without a URL -> { kind: 'text' } + * - shared image file (image/*) -> { kind: 'image' } + */ +export function resolveShareIntent(shareIntent: ShareIntent): SharedPayload | null { + // Shared file(s): pick the first image + if ((shareIntent.type === 'media' || shareIntent.type === 'file') && shareIntent.files?.length) { + const imageFile = shareIntent.files.find((f) => f.mimeType?.startsWith('image/')); + if (imageFile) { + const uri = imageFile.path.startsWith('file://') + ? imageFile.path + : `file://${imageFile.path}`; + return { + kind: 'image', + image: { + uri, + fileName: imageFile.fileName || 'shared-image.jpg', + mimeType: imageFile.mimeType || 'image/jpeg', + size: imageFile.size ?? undefined, + }, + }; + } + return null; + } + + if (shareIntent.type === 'weburl' && shareIntent.webUrl) { + return { kind: 'url', url: shareIntent.webUrl }; + } + + if (shareIntent.type === 'text' && shareIntent.text) { + const url = extractUrlFromSharedText(shareIntent.text); + if (url) return { kind: 'url', url }; + const trimmed = shareIntent.text.trim(); + if (trimmed.length > 0) return { kind: 'text', text: trimmed }; + } + + return null; +} diff --git a/android/didi-app-source/src/utils/socialShareStorage.ts b/android/didi-app-source/src/utils/socialShareStorage.ts new file mode 100644 index 0000000..3e0f3a0 --- /dev/null +++ b/android/didi-app-source/src/utils/socialShareStorage.ts @@ -0,0 +1,60 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import type { SharedPayload } from './shareIntent'; + +const STORAGE_KEY = 'social_share_toggles'; +const PENDING_SHARE_KEY = 'pending_share_url'; + +export interface SocialToggles { + facebook: boolean; + x: boolean; + tiktok: boolean; +} + +const DEFAULT_TOGGLES: SocialToggles = { + facebook: false, + x: false, + tiktok: false, +}; + +export async function loadSocialToggles(): Promise { + try { + const json = await AsyncStorage.getItem(STORAGE_KEY); + if (json) { + const parsed = JSON.parse(json); + if (typeof parsed === 'object' && parsed !== null) { + return { ...DEFAULT_TOGGLES, ...parsed }; + } + } + } catch { + // Corrupted data — delete and return defaults + try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {} + } + return { ...DEFAULT_TOGGLES }; +} + +export async function saveSocialToggles(toggles: SocialToggles): Promise { + await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(toggles)); +} + +export async function savePendingShare(payload: SharedPayload): Promise { + await AsyncStorage.setItem(PENDING_SHARE_KEY, JSON.stringify(payload)); +} + +export async function loadPendingShare(): Promise { + const raw = await AsyncStorage.getItem(PENDING_SHARE_KEY); + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + if (parsed && typeof parsed === 'object' && typeof parsed.kind === 'string') { + return parsed as SharedPayload; + } + } catch { + // Legacy format: plain URL string + if (raw.startsWith('http')) return { kind: 'url', url: raw }; + } + return null; +} + +export async function clearPendingShare(): Promise { + await AsyncStorage.removeItem(PENDING_SHARE_KEY); +} diff --git a/android/didi-app-source/src/utils/uploadConsent.ts b/android/didi-app-source/src/utils/uploadConsent.ts new file mode 100644 index 0000000..c6fd343 --- /dev/null +++ b/android/didi-app-source/src/utils/uploadConsent.ts @@ -0,0 +1,18 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const UPLOAD_CONSENT_KEY = 'media_upload_consent_v1'; + +/** Returns true if the user already gave explicit consent for media uploads. */ +export async function loadUploadConsent(): Promise { + try { + const value = await AsyncStorage.getItem(UPLOAD_CONSENT_KEY); + return value === 'true'; + } catch { + return false; + } +} + +/** Persist the user's explicit consent for media uploads. */ +export async function saveUploadConsent(): Promise { + await AsyncStorage.setItem(UPLOAD_CONSENT_KEY, 'true'); +} diff --git a/android/didi-app-source/src/utils/validation.ts b/android/didi-app-source/src/utils/validation.ts new file mode 100644 index 0000000..11a8472 --- /dev/null +++ b/android/didi-app-source/src/utils/validation.ts @@ -0,0 +1,309 @@ +import { Audio } from 'expo-av'; + +// --------------------------------------------------------------------------- +// Input Validation — Web Parity +// --------------------------------------------------------------------------- + +export type ValidationResult = + | { status: 'valid' } + | { status: 'warning'; message: string } + | { status: 'error'; message: string } + | { status: 'empty' } + | { status: 'error_corrected'; corrected: string }; + +// --------------------------------------------------------------------------- +// Text +// --------------------------------------------------------------------------- + +export function validateText(raw: string): ValidationResult { + const trimmed = raw.trim(); + const len = trimmed.length; + + if (len === 0) return { status: 'empty' }; + if (len < 20) return { status: 'error', message: `Minimum 20 characters (${len}/20)` }; + if (len > 50_000) return { status: 'error', message: `Maximum 50,000 characters exceeded (${len})` }; + return { status: 'valid' }; +} + +// --------------------------------------------------------------------------- +// URL +// --------------------------------------------------------------------------- + +export function validateUrl(raw: string): ValidationResult { + const trimmed = raw.trim(); + if (trimmed.length === 0) return { status: 'empty' }; + if (trimmed.length > 2048) return { status: 'error', message: 'URL too long' }; + + if (/^www\./i.test(trimmed)) { + return { status: 'error_corrected', corrected: 'https://' + trimmed }; + } + + if (!/^https?:\/\//i.test(trimmed)) { + return { status: 'error', message: 'URL must start with http://, https://, or www.' }; + } + + let parsed: URL; + try { + parsed = new URL(trimmed); + } catch { + return { status: 'error', message: 'Invalid URL format' }; + } + + const hostname = parsed.hostname; + if (!hostname) return { status: 'error', message: 'URL must contain a hostname' }; + if (isPrivateHost(hostname)) return { status: 'error', message: 'URL not allowed' }; + if (!hostname.includes('.')) return { status: 'error', message: 'URL must contain a valid domain' }; + + const parts = hostname.split('.'); + const tld = parts[parts.length - 1]; + if (!/^[a-zA-Z]{2,}$/.test(tld)) return { status: 'error', message: 'URL must have a valid top-level domain' }; + + for (const seg of parts) { + if (seg.length === 0 || /\s/.test(seg)) { + return { status: 'error', message: 'URL contains an invalid hostname segment' }; + } + } + + return { status: 'valid' }; +} + +// --------------------------------------------------------------------------- +// Domain +// --------------------------------------------------------------------------- + +const DOMAIN_RE = + /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/; + +export function validateDomain(raw: string): ValidationResult { + const trimmed = raw.trim(); + if (trimmed.length === 0) return { status: 'empty' }; + + if (/^(https?:\/\/|www\.)/i.test(trimmed)) { + return validateUrl(trimmed); + } + + if (isPrivateHost(trimmed)) return { status: 'error', message: 'URL not allowed' }; + + if (!DOMAIN_RE.test(trimmed)) { + return { status: 'error', message: 'Invalid domain format' }; + } + + return { status: 'valid' }; +} + +// --------------------------------------------------------------------------- +// SSRF — Private Host Detection +// --------------------------------------------------------------------------- + +export function isPrivateHost(hostname: string): boolean { + const lower = hostname.toLowerCase(); + + // Localhost variants + if ( + lower === 'localhost' || + lower === 'localhost.localdomain' || + lower.endsWith('.localhost') + ) { + return true; + } + + // Numeric trick: decimal + if (/^\d+$/.test(lower)) { + const num = Number(lower); + if (num === 2130706433) return true; // 127.0.0.1 + if (num >= 0 && num <= 16777215) return true; // 0.x range + if (num >= 167772160 && num <= 184549375) return true; // 10.x + if (num >= 2886729728 && num <= 2887778303) return true; // 172.16-31.x + if (num >= 3232235520 && num <= 3232301055) return true; // 192.168.x + if (num >= 2851995648 && num <= 2852061183) return true; // 169.254.x + } + + // Numeric trick: hex + if (/^0x[0-9a-f]+$/i.test(lower)) { + const num = parseInt(lower, 16); + if (num === 0x7f000001) return true; + } + + // Numeric trick: octal + if (/^0[0-7]+$/.test(lower)) { + const num = parseInt(lower, 8); + if (num === 0o17700000001 || num === 0x7f000001) return true; + } + + // IPv4 + if (/^\d{1,3}(\.\d{1,3}){3}$/.test(lower)) { + const parts = lower.split('.').map(Number); + const [a, b, c, d] = parts; + if (parts.some((p) => p > 255)) return false; + if (a === 127) return true; + if (a === 10) return true; + if (a === 172 && b >= 16 && b <= 31) return true; + if (a === 192 && b === 168) return true; + if (a === 169 && b === 254) return true; + if (a === 0) return true; + if (a === 100 && b >= 64 && b <= 127) return true; + if (a === 198 && b >= 18 && b <= 19) return true; + if (a >= 224) return true; // multicast+ + } + + // IPv6 — bracket-stripped + const v6 = lower.replace(/^\[|]$/g, ''); + if (v6.includes(':')) { + if (v6 === '::' || v6 === '::1') return true; + if (v6.startsWith('fe80:')) return true; + if (v6.startsWith('fc') || v6.startsWith('fd')) return true; + } + + return false; +} + +// --------------------------------------------------------------------------- +// File Validation — Constants +// --------------------------------------------------------------------------- + +const IMAGE_MAX_SIZE = 20 * 1024 * 1024; // 20 MB +const AUDIO_MAX_SIZE = 50 * 1024 * 1024; // 50 MB +const VIDEO_MAX_SIZE = 100 * 1024 * 1024; // 100 MB + +const ALLOWED_IMAGE_MIME = new Set([ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', +]); + +const ALLOWED_AUDIO_MIME = new Set([ + 'audio/mpeg', + 'audio/mp3', + 'audio/wav', + 'audio/wave', + 'audio/x-wav', + 'audio/ogg', + 'audio/flac', + 'audio/aac', + 'audio/mp4', + 'audio/x-m4a', +]); + +const ALLOWED_VIDEO_MIME = new Set([ + 'video/mp4', + 'video/webm', + 'video/ogg', + 'video/quicktime', +]); + +const ALLOWED_IMAGE_EXT = new Set([ + 'jpg', 'jpeg', 'png', 'gif', 'webp', +]); + +const ALLOWED_AUDIO_EXT = new Set([ + 'mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma', +]); + +const ALLOWED_VIDEO_EXT = new Set([ + 'mp4', 'webm', 'ogv', 'mov', +]); + +const BLOCKED_EXT = new Set([ + // Executables + 'exe', 'bat', 'cmd', 'com', 'msi', 'scr', 'pif', + // Scripts + 'sh', 'bash', 'ps1', 'vbs', 'js', 'ws', 'wsf', + // System + 'dll', 'sys', 'cpl', 'inf', 'reg', + // Server-side + 'php', 'jsp', 'asp', 'aspx', 'py', 'rb', 'pl', + // Web + 'html', 'htm', 'svg', 'xml', + // Archives + 'zip', 'rar', '7z', 'tar', 'gz', +]); + +function formatSize(bytes: number): string { + if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${bytes} B`; +} + +export function validateFile( + file: { name: string; type: string; size?: number }, + category: 'image' | 'audio' | 'video', +): ValidationResult { + const ext = (file.name.split('.').pop() || '').toLowerCase(); + + // Blocked extension + if (BLOCKED_EXT.has(ext)) { + return { status: 'error', message: `File type .${ext} is not allowed` }; + } + + // Category-specific checks + const mimeSet = + category === 'image' ? ALLOWED_IMAGE_MIME : + category === 'audio' ? ALLOWED_AUDIO_MIME : + ALLOWED_VIDEO_MIME; + + const extSet = + category === 'image' ? ALLOWED_IMAGE_EXT : + category === 'audio' ? ALLOWED_AUDIO_EXT : + ALLOWED_VIDEO_EXT; + + const maxSize = + category === 'image' ? IMAGE_MAX_SIZE : + category === 'audio' ? AUDIO_MAX_SIZE : + VIDEO_MAX_SIZE; + + const typeOk = mimeSet.has(file.type.toLowerCase()) || extSet.has(ext); + if (!typeOk) { + const accepted = Array.from(extSet).map((e) => `.${e}`).join(', '); + return { status: 'error', message: `Accepted ${category} formats: ${accepted}` }; + } + + if (file.size !== undefined) { + if (file.size === 0) return { status: 'error', message: 'File is empty' }; + if (file.size > maxSize) { + return { + status: 'error', + message: `File too large (${formatSize(file.size)}). Maximum: ${formatSize(maxSize)}`, + }; + } + } + + return { status: 'valid' }; +} + +// --------------------------------------------------------------------------- +// Media Duration Validation +// --------------------------------------------------------------------------- + +const VIDEO_MAX_DURATION = 180; // 3 minutes in seconds +const AUDIO_MAX_DURATION = 420; // 7 minutes in seconds + +/** Get media duration in seconds using expo-av. Returns null if unavailable. */ +export async function getMediaDuration(uri: string): Promise { + try { + const { sound } = await Audio.Sound.createAsync({ uri }, {}, null, false); + const status = await sound.getStatusAsync(); + await sound.unloadAsync(); + if (status.isLoaded && status.durationMillis != null) { + return status.durationMillis / 1000; + } + return null; + } catch { + return null; + } +} + +/** Validate media duration. Returns error message or null if OK. */ +export function validateMediaDuration( + durationSec: number, + category: 'audio' | 'video', +): string | null { + const max = category === 'video' ? VIDEO_MAX_DURATION : AUDIO_MAX_DURATION; + if (durationSec > max) { + const maxMin = Math.floor(max / 60); + const durMin = Math.floor(durationSec / 60); + const durSec = Math.round(durationSec % 60); + return `${category === 'video' ? 'Video' : 'Audio'} too long (${durMin}:${String(durSec).padStart(2, '0')}). Maximum: ${maxMin} minutes.`; + } + return null; +} diff --git a/android/didi-app-source/tsconfig.json b/android/didi-app-source/tsconfig.json new file mode 100644 index 0000000..b9567f6 --- /dev/null +++ b/android/didi-app-source/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true + } +} diff --git a/artefacte/README_LIVRARE.md b/artefacte/README_LIVRARE.md new file mode 100644 index 0000000..cf6497b --- /dev/null +++ b/artefacte/README_LIVRARE.md @@ -0,0 +1,79 @@ +# DiDi Lot 3 (Frontend) — Pachet de livrare + +PNRR DIGI150 · contract 11.1.i3.c9 · Contract de furnizare nr. 19 · 17.07.2026 + +## Conținut + +| Fișier | Descriere | +|---|---| +| `didi-frontend-web_lot3-1.0.docker.tar.gz` | Imaginea Docker a aplicației web (nginx + SPA build) | +| `didi-app-clossers.apk` | Aplicația mobilă Android (release, semnată cu cheie de test) | +| `didi-extension-clossers-v3.2.6.zip` | Extensia de browser Chrome/Edge (Manifest V3) | + +Codul sursă complet: repo `didi-lot3-frontend/` (subfoldere `web/`, `android/didi-app-source/`, +`extension/`) + documentația de livrare în `docs/` (ghid de utilizare 05, matrice de +trasabilitate 06, raport de testare furnizor 07). + +## Configurarea adresei platformei (per componentă) + +Sursele NU conțin adrese hardcodate — adresa serverului DiDi se setează exclusiv în punctele +de configurare de mai jos. + +### 1. Aplicația web — la build (build args) + +```bash +docker build \ + --build-arg VITE_API_URL= \ + --build-arg VITE_KEYCLOAK_URL=https:///auth \ + -t didi-frontend:local ./web +``` + +`VITE_API_URL` gol = same-origin (nginx-ul din imagine proxează `/api`, `/agent-v3` → Kong și +`/auth` → Keycloak, prin rețeaua Docker `didi-network`). nginx-ul răspunde pe orice hostname +(`server_name _`). + +**Rulare** (pe serverul platformei): `docker load < didi-frontend-web_lot3-1.0.docker.tar.gz` +apoi `docker compose -f docker-compose.local.yml up -d` (compose-ul e în rădăcina repo-ului). + +### 2. Aplicația mobilă — la build (.env) + +Fișierul `android/didi-app-source/.env` (model în `.env.example`): + +``` +EXPO_PUBLIC_DIDI_BASE_URL=http:// +``` + +Rebuild: `cd android/didi-app-source/android && ./gradlew assembleRelease` (toolchain-ul și +pașii compleți sunt în `HANDOFF_Lot3_Rebrand_Integrare.md`). APK-ul livrat este construit cu +adresa mediului de integrare. + +### 3. Extensia de browser — fișierul `config.js` + +Singurul loc cu adresa: `extension/config.js` → `DIDI_HOST_HTTP` / `DIDI_HOST_HTTPS`. +După modificare: reîncarcă extensia din `chrome://extensions`. Pe un mediu cu certificat TLS +valid, ambele valori se setează pe `https://…` (separarea HTTP/HTTPS există doar pentru +mediul LAN cu certificat autosemnat). + +## Instalare rapidă componente client + +- **Extensie:** dezarhivează zip-ul → `chrome://extensions` → Developer mode → Load unpacked → + folderul dezarhivat → butonul „Autentificare" (cont DiDi, fără chei API). +- **APK:** transfer pe telefon → instalare din surse necunoscute → Sign In cu contul DiDi. + Dispozitivul trebuie să aibă acces de rețea la adresa platformei. + +## Observații + +- APK-ul e semnat cu cheie de test — pentru publicare în Google Play se generează un keystore + de release. Identificatorul aplicației (`eu.didi365.mobile`) este namespace-ul istoric al + proiectului; schimbarea lui ar crea o aplicație nouă (reinstalare obligatorie pentru + utilizatori) și se face doar la publicarea în store, dacă se dorește. +- Extensia are ID stabil (`blcalchooinkphpnlkkhlklbmgppmnfm`, fixat prin cheia din manifest); + pe acest ID este configurat clientul OIDC `didi-extension` în Keycloak (realm `didi-clients`, + redirect `https://.chromiumapp.org/*`). La schimbarea mediului, clientul Keycloak se + recreează identic pe noul realm/server. +- Fișierele istorice scoase din repo la curățenia din 17.07.2026 sunt păstrate în + `~/didi-lot3-frontend-arhiva/`. + +## Sume de control (SHA-256) + +Vezi `SHA256SUMS.txt` din acest folder. diff --git a/artefacte/SHA256SUMS.txt b/artefacte/SHA256SUMS.txt new file mode 100644 index 0000000..79023d7 --- /dev/null +++ b/artefacte/SHA256SUMS.txt @@ -0,0 +1,3 @@ +887e7bfbdae34e370e8863dee9aa44727e02f5aff44a721bb600edd0f990b79c didi-frontend-web_lot3-1.0.docker.tar.gz +295c902207efdb82617e7a358d680b114eb7b846869ff7ef8a09b1277b79fd1b didi-app-clossers.apk +f35d506539e66da96f71515beada803b42c2758ef2ac4922e745c3f3c6ffa7bc didi-extension-clossers-v3.2.6.zip diff --git a/artefacte/didi-app-clossers.apk b/artefacte/didi-app-clossers.apk new file mode 100644 index 0000000..2618bfb Binary files /dev/null and b/artefacte/didi-app-clossers.apk differ diff --git a/artefacte/didi-extension-clossers-v3.2.6.zip b/artefacte/didi-extension-clossers-v3.2.6.zip new file mode 100644 index 0000000..835119f Binary files /dev/null and b/artefacte/didi-extension-clossers-v3.2.6.zip differ diff --git a/artefacte/didi-frontend-web_lot3-1.0.docker.tar.gz b/artefacte/didi-frontend-web_lot3-1.0.docker.tar.gz new file mode 100644 index 0000000..a65f961 Binary files /dev/null and b/artefacte/didi-frontend-web_lot3-1.0.docker.tar.gz differ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..cadff6d --- /dev/null +++ b/build.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================ +# DIDI Frontend - Build Script +# Checks prerequisites, builds Docker production image +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WEB_DIR="$SCRIPT_DIR/web" +IMAGE_NAME="${DIDI_IMAGE_NAME:-didi-frontend}" +IMAGE_TAG="${DIDI_IMAGE_TAG:-latest}" +VITE_API_URL="${VITE_API_URL:-}" + +# -- Colors ------------------------------------------------------------------ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +log() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERR]${NC} $*" >&2; } +step() { echo -e "\n${CYAN}==> $*${NC}"; } + +# -- Usage ------------------------------------------------------------------- +usage() { + cat </dev/null; then + log "Docker: $(docker --version 2>/dev/null | head -1)" + else + err "Docker not installed. https://docs.docker.com/get-docker/" + ok=false + fi + + # Docker daemon running + if command -v docker &>/dev/null; then + if docker info &>/dev/null; then + log "Docker daemon: running" + else + err "Docker daemon not running. Try: sudo systemctl start docker" + ok=false + fi + fi + + # Required files + for f in package.json Dockerfile nginx.conf index.html; do + if [[ -f "$WEB_DIR/$f" ]]; then + log "$f: found" + else + err "$f not found in $WEB_DIR" + ok=false + fi + done + + # src/ directory + if [[ -d "$WEB_DIR/src" ]]; then + log "src/: found" + else + err "src/ directory not found" + ok=false + fi + + if ! $ok; then + err "Prerequisites check failed." + exit 1 + fi +} + +# -- Resolve API URL --------------------------------------------------------- +resolve_api_url() { + if [[ -z "$VITE_API_URL" && -f "$WEB_DIR/.env.local" ]]; then + VITE_API_URL=$(grep -E '^VITE_API_URL=' "$WEB_DIR/.env.local" 2>/dev/null | cut -d'=' -f2- || true) + fi + + if [[ -n "$VITE_API_URL" ]]; then + log "VITE_API_URL = $VITE_API_URL" + else + warn "VITE_API_URL is empty (same-origin mode)" + fi +} + +# -- Docker build ------------------------------------------------------------ +docker_build() { + step "Building Docker image: ${IMAGE_NAME}:${IMAGE_TAG}" + + local build_args=() + if [[ -n "$VITE_API_URL" ]]; then + build_args+=(--build-arg "VITE_API_URL=${VITE_API_URL}") + fi + + if $DRY_RUN; then + warn "[dry-run] docker build ${build_args[*]+"${build_args[*]}"} -t ${IMAGE_NAME}:${IMAGE_TAG} $WEB_DIR" + return + fi + + docker build \ + ${build_args[@]+"${build_args[@]}"} \ + -t "${IMAGE_NAME}:${IMAGE_TAG}" \ + "$WEB_DIR" + + log "Image built successfully" + + step "Image details" + docker images "${IMAGE_NAME}:${IMAGE_TAG}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" +} + +# -- Verify ------------------------------------------------------------------ +verify_image() { + step "Verifying image (smoke test)" + + if $DRY_RUN; then + warn "[dry-run] Would start temp container, check /health, remove it" + return + fi + + local cname="didi-verify-$$" + + docker run -d --name "$cname" -p 0:80 "${IMAGE_NAME}:${IMAGE_TAG}" >/dev/null 2>&1 + local port + port=$(docker port "$cname" 80 | head -1 | cut -d: -f2) + + local retries=10 + local healthy=false + while (( retries > 0 )); do + if curl -sf "http://localhost:${port}/health" >/dev/null 2>&1; then + healthy=true + break + fi + sleep 0.5 + (( retries-- )) + done + + docker rm -f "$cname" >/dev/null 2>&1 + + if $healthy; then + log "Health check PASSED" + else + warn "Health check FAILED — image built but nginx may have issues" + fi +} + +# -- Summary ----------------------------------------------------------------- +summary() { + echo "" + echo -e " ${BOLD}Build complete${NC}" + echo "" + echo " Image: ${IMAGE_NAME}:${IMAGE_TAG}" + echo " API: ${VITE_API_URL:-}" + echo "" + echo " Run: docker run -d -p 8080:80 ${IMAGE_NAME}:${IMAGE_TAG}" + echo " Test: curl http://localhost:8080/health" + echo "" +} + +# -- Main -------------------------------------------------------------------- +main() { + echo "" + echo -e " ${BOLD}DIDI Frontend Build${NC}" + echo "" + + check_prerequisites + resolve_api_url + docker_build + + if ! $SKIP_VERIFY; then + verify_image + fi + + summary +} + +main diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..838fa46 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,30 @@ +# DiDi Frontend — deployment local pe serverul de integrare (10.11.10.18) +# Build: docker build --build-arg VITE_API_URL= --build-arg VITE_KEYCLOAK_URL=https://10.11.10.18/auth -t didi-frontend:local ./web +# Run: docker compose -f docker-compose.local.yml up -d +name: didi-frontend + +services: + didi-frontend: + image: didi-frontend:local + container_name: didi-frontend + restart: unless-stopped + ports: + - "0.0.0.0:80:80" + - "0.0.0.0:443:443" + volumes: + # Pachetul de livrare (imagine docker salvată, APK, extensie, README, SHA256SUMS), + # servit la /downloads/livrare/ — montat de pe host ca tar-ul imaginii să fie + # întotdeauna exact imaginea care rulează (nu poate fi copt în propria imagine). + - /home/topclossers/didi-lot3-livrare:/usr/share/nginx/html/downloads/livrare:ro + networks: + - didi-network + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + +networks: + didi-network: + external: true diff --git a/docs/05_Ghid_Utilizare_Lot3.docx b/docs/05_Ghid_Utilizare_Lot3.docx new file mode 100644 index 0000000..eb5dac5 Binary files /dev/null and b/docs/05_Ghid_Utilizare_Lot3.docx differ diff --git a/docs/05_Ghid_Utilizare_Lot3.md b/docs/05_Ghid_Utilizare_Lot3.md new file mode 100644 index 0000000..63a40fe --- /dev/null +++ b/docs/05_Ghid_Utilizare_Lot3.md @@ -0,0 +1,269 @@ +% Ghid de utilizare — DiDi Lot 3 (Frontend) +% Aplicația web · Aplicația mobilă · Extensia de browser +% PNRR DIGI150 · contract 11.1.i3.c9 + +--- + +# 1. Introducere + +Platforma DiDi analizează conținut (text, linkuri, imagini, audio, video) pentru semne de +dezinformare și returnează un **raport de analiză** cu: scor de risc (0–100), verdict, grad de +certitudine, explicație, tehnici de manipulare detectate, verificarea afirmațiilor cu surse, +evaluarea sursei și detecția conținutului generat de AI. + +Platforma se folosește prin **trei aplicații client**, cu **un singur cont** (autentificare unică): + +| Aplicație | Când o folosești | +|---|---| +| **Aplicația web** | analiza completă, istoric, rapoarte PDF, administrarea contului | +| **Aplicația mobilă** | analiză din mers; partajezi conținut din orice aplicație direct către DiDi | +| **Extensia de browser** | verifici pagini, texte și imagini direct în timpul navigării | + +**Contul tău:** același email + parolă funcționează în toate cele trei aplicații. Autentificarea +se face prin serverul securizat al platformei (Keycloak); aplicațiile nu văd și nu stochează +parola ta. + +**Confidențialitate:** conținutul trimis este folosit exclusiv pentru generarea raportului de +analiză. Trimiterea fișierelor și a capturilor de ecran necesită **consimțământul tău explicit**, +cerut o singură dată. Poți șterge oricând analizele din Istoric. + +--- + +# 2. Aplicația web + +**Acces:** `https://10.11.10.18/` (sau adresa comunicată de administrator). La certificat +autosemnat, browserul afișează un avertisment — alege „Advanced" → „Continue". + +## 2.1 Autentificare + +1. Pe pagina de prezentare apasă **„Începe analiza"**. +2. Ești redirecționat către formularul de autentificare — introdu emailul și parola + (sau creează cont nou cu „Register"; recuperarea parolei — „Forgot password"). +3. După autentificare ajungi în **Dashboard**. + +![Formularul de autentificare](media_testare/web-02-login-keycloak.png){width=15cm} + +## 2.2 Analiza completă + +1. În meniul lateral alege **„Analiză completă"**. +2. Alege tab-ul potrivit conținutului: **Text / URL / Imagine / Audio / Video**. +3. Introdu conținutul: lipește textul sau linkul, ori selectează fișierul de pe disc. +4. Pentru fișiere: la prima trimitere bifează **consimțământul** (butonul de analiză rămâne + inactiv până atunci). +5. Apasă **„Rulează analiza completă"** și urmărește progresul pe componente. + +![Formularul unificat cu cele 5 tipuri de conținut](media_testare/web-03-formular-unificat.png){width=15cm} + +![Consimțământul pentru fișiere (o singură dată)](media_testare/web-04-consimtamant-upload.png){width=15cm} + +![Analiza în desfășurare — progres pe componente](media_testare/web-05-progres-analiza.png){width=15cm} + +## 2.3 Citirea raportului + +Raportul afișează, de sus în jos: + +- **Scorul de risc** (0–100) cu inel colorat: verde = sigur … roșu = pericol; +- **Verdictul** (De încredere → Dezinformare), nivelul de risc și certitudinea; +- **Explicația** în limba română — rezumatul motivelor; +- **Acțiunea recomandată** (ex. „Nu distribui acest articol"); +- **Cea mai gravă problemă**, cu citat-dovadă; +- Secțiuni expandabile cu detalii per componentă: tehnici de manipulare, afirmații verificate + (cu surse care susțin/contrazic), detecție AI, evaluarea sursei. + +![Exemplu de raport: verdict, explicație, acțiune recomandată](media_testare/web-06-rezultat-verdict.png){width=15cm} + +**Partajare și export:** butoanele **„Partajează"** (copiază un link către raport, accesibil +utilizatorilor autentificați) și **„Descarcă PDF"** se află în colțul din dreapta sus al +raportului. + +![Butoanele Partajează și Descarcă PDF pe un raport](media_testare/web-07-rezultat-video-partajare.png){width=15cm} + +## 2.4 Analize pe competențe individuale + +Din meniul **„Abilități DIDI"** poți rula o singură componentă, mai rapid decât pipeline-ul +complet: **Tehnici de manipulare**, **Evaluarea sursei**, **Verificarea afirmațiilor**, +**Detectare AI** — fiecare cu propriul formular și ecran de rezultate. + +![Competența „Detectare AI"](media_testare/web-08-competenta-detectare-ai.png){width=15cm} + +## 2.5 Istoric + +Meniul **„Istoric"** listează analizele tale: scor, verdict, tip conținut, dată. De aici poți: + +- **filtra** după căutare în conținut, nivel de risc, status, tip de conținut, interval de date; +- deschide orice analiză anterioară (click pe element); +- descărca **PDF**-ul sau **șterge** analiza (cu confirmare). + +![Istoricul cu rândul de filtre](media_testare/web-09-istoric-filtre.png){width=15cm} + +## 2.6 Setări + +Meniul **„Setări"**: profilul contului, **schimbarea parolei** (prin serverul de autentificare), +abonamentul și creditele. Tot aici descarci celelalte două aplicații: + +- **Extensia de browser** — arhiva .zip + instrucțiuni de instalare pas cu pas; +- **Aplicația mobilă** — fișierul APK pentru Android. + +Ambele sunt disponibile **pentru orice tip de abonament**. + +![Setări: descărcarea extensiei și a aplicației mobile](media_testare/web-11-setari-descarcari.png){width=15cm} + +--- + +# 3. Aplicația mobilă (Android) + +## 3.1 Instalare și autentificare + +1. Descarcă APK-ul din aplicația web (Setări → Aplicație mobilă) și deschide-l pe telefon + (acceptă instalarea din surse necunoscute, dacă ți se cere). +2. Telefonul trebuie să fie în aceeași rețea cu platforma. +3. Deschide **didi** → **Sign In** → introdu emailul și parola în formularul securizat. + +![Autentificarea în aplicația mobilă](media_testare/mob-01-login-keycloak.png){width=7.5cm} + +## 3.2 Analiza conținutului + +Din **Dashboard** → **Analiză completă**: alege tab-ul (Text / Imagine / Audio / Video / URL), +introdu conținutul și apasă **„Rulează analiza completă"**. + +![Dashboard-ul aplicației mobile](media_testare/mob-02-dashboard.png){width=7.5cm} + +**Imagini cu decupare:** la tab-ul Imagine, după ce alegi poza din galerie sau o fotografiezi, +se deschide **editorul de decupare** — păstrezi doar regiunea relevantă, apoi confirmi. Imaginea +decupată apare în preview și se trimite la analiză. + +![Imaginea decupată, gata de analiză](media_testare/mob-05-crop-imagine-preincarcata.png){width=7.5cm} + +**Consimțământ:** la prima trimitere a unui fișier apare fereastra de consimțământ — bifează +acordul și continuă; nu se va mai cere data următoare. + +![Consimțământul pentru încărcarea fișierelor](media_testare/mob-06-consimtamant.png){width=7.5cm} + +Rezultatul se afișează în format optimizat pentru mobil: verdict + scor, secțiuni expandabile, +surse cu linkuri, recomandări. + +![Raport de analiză pe mobil](media_testare/mob-07-rezultat.png){width=7.5cm} + +## 3.3 Share-to-Analyze — analizează din orice aplicație + +Cel mai rapid mod de a folosi DiDi pe telefon: **partajezi conținut direct către didi** din orice +aplicație (browser, social media, galerie, mesagerie): + +1. În aplicația-sursă apasă **Share** și alege **didi** din listă. +2. DiDi deschide ecranul de analiză cu conținutul preluat — **link**, **text** sau **imagine**. +3. Alege tipul de analiză (completă sau o competență individuală) — analiza pornește imediat. + +Dacă nu ești autentificat, aplicația te duce întâi la login și **revine automat** la analiza ta. + +![Share dintr-o aplicație terță către didi](media_testare/mob-08-share-imagine-sursa.png){width=7.5cm} + +![Conținutul partajat, cu opțiunile de analiză](media_testare/mob-09-share-imagine-ecran.png){width=7.5cm} + +![Link partajat — inclusiv analiza domeniului](media_testare/mob-12-share-link.png){width=7.5cm} + +## 3.4 Istoric și Setări + +- **Istoric:** analizele tale, cu derulare infinită; analizele în curs se actualizează automat; + ștergerea cere confirmare; atingerea unui element deschide raportul complet. +- **Setări:** profil, schimbare parolă, limbă, opțiuni de analiză automată pentru linkurile + partajate din rețele sociale și **Deconectare** (revocă sesiunea de pe dispozitiv). + +![Istoricul pe mobil](media_testare/mob-13-istoric.png){width=7.5cm} + +![Deconectarea, din Setări](media_testare/mob-14-setari-deconectare.png){width=7.5cm} + +--- + +# 4. Extensia de browser (Chrome / Edge) + +## 4.1 Instalare + +1. Din aplicația web → **Setări → Extensie browser** → **„Descarcă extensia (.zip)"**. +2. Dezarhivează arhiva într-un folder. +3. Deschide `chrome://extensions`, activează **Developer mode** (dreapta sus), apasă + **„Load unpacked"** și alege folderul dezarhivat. + +![Extensia instalată în Chrome](media_testare/ext-01-instalare.png){width=13cm} + +## 4.2 Autentificare + +Deschide popup-ul extensiei (iconița **d** din bara Chrome) → **„Autentificare"** → se deschide +fereastra de login a platformei → introdu emailul și parola. După autentificare, emailul tău apare +în antetul popup-ului. **„Deconectare"** (jos) închide sesiunea. + +![Popup-ul înainte de autentificare](media_testare/ext-02-popup-neconectat.png){width=9cm} + +## 4.3 Ce poți analiza, direct din browser + +Din **popup**: + +- **Pagina curentă** — câmpul URL e precompletat; apasă „Pagina curentă" sau „Analizează URL"; +- **Orice URL** — lipește-l în câmpul dedicat; +- **Text** — selectează textul în pagină înainte să deschizi popup-ul (e preluat automat) sau + lipește-l manual (max 5000 de caractere); +- **Screenshot** — trasezi o regiune pe ecran; extensia analizează exact ce ai selectat; +- **Record Video** — înregistrezi o regiune a paginii (ex. un clip din feed). + +![Secțiunile de analiză din popup](media_testare/ext-04-popup-url-text.png){width=9cm} + +Din **meniul contextual** (click-dreapta), fără să deschizi popup-ul: + +- click-dreapta pe **o imagine** → „Analizează imaginea cu DiDi"; +- click-dreapta pe **text selectat** → „Analizează textul selectat cu DiDi"; +- click-dreapta oriunde în pagină → „Analizează pagina curentă cu DiDi". + +![Meniul contextual pe o imagine](media_testare/ext-08-context-menu-imagine.png){width=15cm} + +**Consimțământ:** la prima captură sau imagine trimisă apare fereastra de consimțământ direct în +pagină — „Sunt de acord" pornește analiza; alegerea se reține. + +![Consimțământul pentru capturi](media_testare/ext-06-consimtamant.png){width=15cm} + +**Captura cu selecție de regiune:** după „Screenshot", pagina se întunecă ușor — trage cu mouse-ul +peste zona de analizat; extensia decupează exact acea regiune. + +![Selectarea regiunii de analizat](media_testare/ext-07-snipping-selectie.png){width=15cm} + +## 4.4 Rezultatele + +- Analizele pornite **din pagină** (screenshot, video, click-dreapta) afișează raportul ca + **fereastră peste pagină** când analiza e gata (30–90 s). +- Analizele pornite **din popup** (text/URL) deschid raportul într-un **tab nou**. +- Raportul conține aceleași secțiuni ca în aplicația web: verdict cu scor, explicație completă, + detecție AI, afirmații verificate cu surse, evaluarea sursei. + +![Raport de analiză în extensie — verdict și explicație](media_testare/ext-09-rezultat-verdict.png){width=12.5cm} + +![Detecție AI și verificarea afirmațiilor](media_testare/ext-10-rezultat-componente.png){width=12.5cm} + +## 4.5 Istoric + +Butonul **„Istoric"** din popup deschide pagina cu ultimele analize (păstrate local, max 50) — +poți redeschide detaliile oricărei analize sau o poți șterge. + +![Istoricul extensiei](media_testare/ext-12-istoric.png){width=15cm} + +--- + +# 5. Întrebări frecvente + +**Cum interpretez scorul?** 0–30 (verde) = conținut în general de încredere; 31–60 (galben) = +îndoielnic, citește critic; 61–100 (portocaliu/roșu) = risc ridicat — verifică sursele oficiale și +nu distribui. + +**De ce un conținut cu afirmații adevărate poate primi verdict de risc ridicat?** Pentru că +riscul nu vine doar din afirmații: o imagine generată de AI care „ambalează" o știre reală este +o prezentare falsificată — sistemul o detectează și explică exact acest lucru în raport. + +**Mi s-a epuizat creditul.** Fiecare analiză consumă credite în funcție de tipul conținutului +(text 1, imagine 2, video 5). La epuizare primești mesajul „Insufficient credits" — creditele se +reînnoiesc conform abonamentului (vezi Setări → Abonament). + +**Analiza durează mult.** Textele durează secunde; imaginile ~30–90 s; clipurile video pot dura +câteva minute (transcriere + analiză vizuală). Poți închide fereastra — rezultatul apare în +Istoric. + +**Pot șterge o analiză?** Da — din Istoric (web/mobil: ștergere cu confirmare; extensie: ștergere +din pagina de istoric). Ștergerea elimină și conținutul asociat. + +*Versiune document: 1.0 · 17.07.2026* diff --git a/docs/06_Matrice_Trasabilitate_Cerinte_Lot3.docx b/docs/06_Matrice_Trasabilitate_Cerinte_Lot3.docx new file mode 100644 index 0000000..207202c Binary files /dev/null and b/docs/06_Matrice_Trasabilitate_Cerinte_Lot3.docx differ diff --git a/docs/06_Matrice_Trasabilitate_Cerinte_Lot3.md b/docs/06_Matrice_Trasabilitate_Cerinte_Lot3.md new file mode 100644 index 0000000..00b480c --- /dev/null +++ b/docs/06_Matrice_Trasabilitate_Cerinte_Lot3.md @@ -0,0 +1,155 @@ +% Matrice de trasabilitate a cerințelor — DiDi Lot 3 (Frontend) +% PNRR DIGI150 · contract 11.1.i3.c9 · Contract de furnizare nr. 19 + +--- + +# 1. Scop + +Prezentul document asigură **trasabilitatea** dintre cerințele contractuale (Caietul de sarcini, +secțiunea LOT 3 + Propunerea Tehnică Lot 3) și livrabilele efective ale Lotului 3: +**aplicația web**, **aplicația mobilă (Android/iOS)** și **extensia de browser**. Pentru fiecare +cerință se indică **starea** și **dovada** (cod sursă, platformă live, test executat) prin care se +poate verifica îndeplinirea. + +Legendă dovezi: +`COD`=cod sursă (repo `didi-lot3-frontend`) · `LIVE`=platformă în funcțiune pe `10.11.10.18` · +`TEST`=test executat la 15.07.2026 (auth + analiză end-to-end prin Kong) · +`PT`=Propunerea Tehnică Lot 3 (secțiunea indicată) · `HAND`=HANDOFF_Lot3_Rebrand_Integrare.md. + +Stare: ✅ conform · ⚠️ conform cu observație asumată (detaliat în §6). + +Livrabile: app web LIVE `https://10.11.10.18/` · APK `~/didi-app-clossers.apk` · +extensie `~/didi-extension-clossers.zip` (sau `didi-lot3-frontend/extension/`). + +--- + +# 2. Componenta 1 — Aplicație Web (`web/`) + +| # | Cerință (caiet de sarcini) | Stare | Dovadă | +|---|---|---|---| +| W1 | Trimitere conținut pentru analiză: text, URL, imagini, audio, video | ✅ | COD `PipelineAnalysis/sections/InputForm.tsx`, `AnalysisTab.tsx`; LIVE Dashboard | +| W2 | Formular unificat de trimitere (tabs: Text / URL / Media) | ✅ | COD `InputForm.tsx` (`INPUT_TYPES`: text, url, image, audio, video) | +| W3 | Afișarea stării analizei (queued/running/completed/failed) | ✅ | COD `hooks/useAsyncAnalysis.ts` (polling 2,5s, stări complete); PT §1.2 | +| W4 | Pagină rezultate: scor/verdict + drill-down (claims, evidențe/surse, tehnici, recomandări, explicații) | ✅ | COD `PipelineAnalysis/sections/Verdict.tsx` + `details/{techniques,claims,source,ai}.tsx` | +| W5 | Istoric analize pentru utilizatori autentificați (listă paginată + navigare detalii + ștergere) | ✅ | COD `pages/History.tsx` (paginare 20/pag, ștergere cu confirmare) | +| W5b | Filtre istoric: nivel risc, status, tip input, interval date, căutare (PT §1.2) | ⚠️ | COD `pages/History.tsx` (rând filtre complet; filtrare client-side — v. §6.4) | +| W6 | Export / partajare raport | ✅ | COD `Reports/DownloadPdfButton.tsx` (export PDF) + `Reports/ShareReportButton.tsx` (navigator.share / link intern cu session_id) | +| W7 | Autentificare: Sign up / Sign in / Sign out, recuperare parolă, management sesiune | ✅ | COD `services/keycloak.service.ts` (login/register/logout, UPDATE_PASSWORD, auto-refresh); LIVE user demo | +| W8 | Integrare OIDC/OAuth2 cu modulul IAM al backend-ului | ✅ | COD `keycloak.service.ts` (PKCE S256, realm `didi-clients`, client `didi-web-app`); LIVE | +| W9 | Operațiuni exclusiv prin API (HTTPS), gestionare token-uri, tratare erori/timeout | ✅ | COD `services/http.client.ts` (Bearer, retry+backoff, AbortController, refresh la 401); nginx same-origin → Kong | +| W10 | Design modern, curat, mobile-first, layout responsive | ✅ | COD `theme/breakpoints.ts`, `theme/tokens.ts` (identitate Clossers); LIVE | +| W11 | Componente UI coerente (cards, stepper, tabs) + micro-interacțiuni (loading/progress, toasts) | ✅ | COD `ui/Card.tsx`, `ui/Button.tsx`, `AnalysisTab` (stepper), `Toast/Toast.tsx` | +| W12 | WCAG 2.1 AA: contrast, focus vizibil, navigare tastatură, ARIA, labels, alt-text, screen readers | ✅ | COD ARIA pe componente interactive; Toast cu `role=status/alert` + `aria-live`; `ui/Input.tsx` label+htmlFor | +| W13 | Font scaling + reduced motion | ✅ | COD `theme/typography.ts` (px→rem, clamp pe titluri); `App.css` + `LandingPage.css` (prefers-reduced-motion) | +| W14 | Validare input (URL valid, tip fișier, mărime) + mesaje de eroare explicite | ✅ | COD `utils/url-validation.ts`, `utils/file-validation.ts` (MIME+extensii+limite 20/50/100MB), `text-validation.ts` | +| W15 | Protecție SSRF la input URL | ✅ | COD `utils/url-validation.ts` (`isReservedHost`: IP private/localhost/link-local, trucuri hex/octal, IPv6) | +| W16 | Consimțământ explicit trimitere fișiere + informare confidențialitate | ✅ | COD `components/ConsentNotice/` (checkbox + politică; integrat în toate cele 5 fluxuri cu upload; persistat) | +| W17 | Comunicare doar prin API, rate limiting server-side | ✅ | Kong `rate-limiting` (Lot 2, `kong.yml.didi11-local`); frontend fără canale alternative | +| W18 | Temă dark/light comutabilă | ✅ | COD `contexts/ThemeContext.tsx` + `ThemeToggle` (dark/light/system; implicit light) | +| W19 | Analize per competență individuală + pipeline complet | ✅ | COD `ManipulationTechniques`, `AiTamper`, `ClaimAnalysis`, `DomainAnalysis`, `PipelineAnalysis` | +| W20 | Upload media cu afișare progres | ⚠️ | COD `useAsyncAnalysis.ts` (stare `uploading` + bară de progres; procent euristic — v. §6.5) | + +--- + +# 3. Componenta 2 — Aplicație Mobilă (`android/didi-app-source/`) + +| # | Cerință (caiet de sarcini) | Stare | Dovadă | +|---|---|---|---| +| M1 | Funcționalitate echivalentă web: trimitere + analiză text/URL/imagine/audio/video, UI mobilă | ✅ | COD `screens/PipelineScreen.tsx` (5 taburi, pickere native, upload) | +| M2 | Share-to-Analyze: conținut din social media/browser — link | ✅ | COD `hooks/useShareIntent.ts`, `utils/shareIntent.ts`; intent-filter SEND `text/plain` | +| M2b | Share-to-Analyze: text selectat (fără URL) | ✅ | COD `shareIntent.ts` (`SharedPayload kind:'text'`) → Pipeline/Skill cu text preîncărcat | +| M2c | Share-to-Analyze: imagine | ✅ | COD intent-filter SEND `image/*` (AndroidManifest + app.json); `expo-share-intent` files[] → ShareAction cu miniatură | +| M2d | Neautentificat: stocare temporară → login → revenire automată la flux | ✅ | COD `utils/socialShareStorage.ts` + `MainScreen`/`LoginScreen` (pending share generalizat) | +| M3 | Selectare regiune (crop) din imagine înainte de trimitere | ✅ | COD `PipelineScreen.tsx` + `SkillAnalysisScreen.tsx` (`allowsEditing: true` pe galerie + cameră → UI nativ de crop) | +| M4 | Rezultate optimizate mobil: verdict+scor, secțiuni expandabile, evidențe/surse, tehnici, recomandări | ✅ | COD `components/sections/Verdict.tsx`, `editorial/CollapseButton.tsx`, `ActionCallout.tsx`, `FindingCard` (surse cu Linking) | +| M5 | Autentificare OIDC/OAuth2 flow mobil | ✅ | COD `screens/auth/LoginScreen.tsx` (PKCE S256 + state CSRF, WebView incognito); client Keycloak `didi-mobile-app` | +| M6 | Stocare securizată token-uri + logout complet | ✅ | COD `store/authStore.ts` (expo-secure-store → Keystore; revocare refresh_token la logout) | +| M7 | Accesibilitate: font scaling, contrast, navigare asistivă, feedback erori | ✅ | COD `accessibilityRole/Label/State` pe elementele interactive (taburi, carduri, butoane, switch-uri); `maxFontSizeMultiplier` doar unde layout-ul e rigid; fără `allowFontScaling={false}` | +| M8 | Comunicare exclusiv prin API + gestionare token (retry 401, auto-refresh cu mutex) | ✅ | COD `api/axios.ts` (interceptori request/response), `authStore.ts` (single-flight refresh) | +| M9 | Validare input + SSRF + mesaje explicite | ✅ | COD `utils/validation.ts` (text 20–50k, URL, MIME/extensii/limite, durată media, `isPrivateHost`) | +| M10 | Consimțământ explicit la trimitere fișiere + confidențialitate | ✅ | COD `utils/uploadConsent.ts` + `components/UploadConsentModal.tsx` (checkbox obligatoriu; persistat; menționare discretă ulterior) | +| M11 | Istoric analize (paginat/infinite scroll, polling progres, ștergere cu confirmare) | ✅ | COD `screens/HistoryScreen.tsx` + `api/history.ts` | +| M12 | Competențe individuale + pipeline complet | ✅ | COD `SkillsScreen.tsx` (4 competențe) + `SkillAnalysisScreen.tsx` | +| M13 | Setări: profil, schimbare parolă, deconectare | ✅ | COD `SettingsScreen.tsx` + `SignOutSection.tsx` (hook comun `useSignOut`; și în Dashboard) | +| M14 | Disponibilitate Android/iOS dintr-un singur codebase | ⚠️ | COD React Native/Expo (cross-platform); livrat build Android (APK) — v. §6.2 | + +--- + +# 4. Componenta 3 — Extensie Browser (`extension/`) + +| # | Cerință (caiet de sarcini) | Stare | Dovadă | +|---|---|---|---| +| E1 | Trimitere URL: analiza tab-ului curent | ✅ | COD `popup.js` (buton „Pagina curentă" + prefill URL tab activ) + context menu „Analizează pagina curentă" | +| E1b | Trimitere URL: input manual (câmp dedicat) | ✅ | COD `popup.html` (`#urlInput` + validare http/https) → `background.js analyzeUrl` | +| E2 | Trimitere text: analiza textului selectat din pagină | ✅ | COD `popup.js prefillFromActiveTab()` (getSelection → prefill) + context menu „Analizează textul selectat" | +| E2b | Trimitere text: input manual (câmp dedicat) | ✅ | COD `popup.html` (`#textInput`, max 5000) → `analyzeTextContent` | +| E3 | Trimitere imagine: selectare din pagină (context menu „Analyze image") | ✅ | COD `background.js` (`chrome.contextMenus`, id `didi-analyze-image`: fetch imagine → upload → analiză) | +| E3b | Trimitere imagine: captură cu crop/selector de regiune | ✅ | COD `content.js` (snipping overlay + drag select) + `background.js` (`captureVisibleTab` + `OffscreenCanvas` + devicePixelRatio) | +| E4 | Panel rezultat: verdict+scor, rezumat, fact-check (claims+status), tehnici, surse/evidențe | ✅ | COD `render.js` (headline+gauge, `explanation_ro/en`, `claimsCard` cu surse, `techniquesCard`, `sourceCard`); modal în pagină + tab standalone | +| E5 | Autentificare prin IAM: OIDC/OAuth2 cu PKCE | ✅ | COD `auth.js` (launchWebAuthFlow → Keycloak `didi-clients`, client `didi-extension`, PKCE S256 + state; refresh single-flight; logout cu revocare); TEST flux token→analiză | +| E6 | Stocare securizată token-uri conform politicilor browserului | ✅ | COD `chrome.storage.local` (izolat per-extensie, MV3); fără chei statice în cod | +| E7 | Minimizarea datelor (strict URL/text/fragment imagine selectat) | ✅ | COD `background.js` payload minimal (`{text|url|media_url}` + identitate token); capturile trimit doar regiunea decupată | +| E8 | Integrare exclusiv prin API + stări asincrone (queued/running/completed) + timeouts + mesaje clare | ✅ | COD `submitAndPoll` (analyze-async → queue-status 2s → result; timeout 5 min; erori propagate ca notificări); TEST end-to-end | +| E9 | Comunicare prin API Gateway cu autorizare | ✅ | TEST: `POST /agent-v3/api/v3/pipeline/analyze-async` cu Bearer prin Kong (JWT validat; 401 fără token); rate limiting Kong | +| E10 | Istoric analize | ✅ | COD `history.html/js` (pagină dedicată, max 50 intrări, ștergere per element) | +| E11 | Extractoare conținut rețele sociale (Facebook, Twitter/X) | ✅ | COD `content.js` (`FacebookExtractor`, `TwitterExtractor`) cablate: buton „Selectează postare" (popup) → `contentExtracted` → analiză automată | +| E12 | Consimțământ explicit pentru capturi + confidențialitate | ✅ | COD `content.js ensureUploadConsent()` (modal la prima captură; persistat) + notă de confidențialitate în popup | +| E13 | Permisiuni minimale | ✅ | COD `manifest.json` v3.2.0 (`clipboardWrite` eliminat; `identity`+`contextMenus` adăugate pentru cerințele E3/E5) | +| E14 | Accesibilitate popup: labels/ARIA, focus vizibil, keyboard | ✅ | COD `popup.html` (aria-label pe toate controalele, `aria-live` pe status) + `popup.css` (`:focus-visible`) | + +--- + +# 5. Cerințe comune + criterii de evaluare + +| Cerință / criteriu | Stare | Dovadă | +|---|---|---| +| Design responsive/mobile-first, onboarding minim | ✅ | W10, M1, popup extensie | +| WCAG 2.1 AA pe web + extensie (focus/contrast/tastatură/labels/ARIA — criteriul 5) | ✅ | W12–W13, E14 | +| Validare input + mesaje explicite (toate componentele) | ✅ | W14, M9, E8 | +| Consimțământ explicit fișiere/capturi (toate componentele) | ✅ | W16, M10, E12 | +| Comunicare doar prin API, rate limiting și protecții server-side | ✅ | W17, M8, E9 (Kong: JWT + rate limiting + request-size-limiting) | +| E2 evaluare — IAM OIDC/OAuth2 funcțional pe web + mobil + extensie, TLS, validare token la gateway | ✅ | Toate cele 3 componente pe Keycloak realm `didi-clients` (clienți `didi-web-app`, `didi-mobile-app`, `didi-extension`); Kong validează JWT (TEST) | +| E4 evaluare — wireframes/mockups mapate pe endpoint-uri și stări | ✅ | PT Anexa C; implementare 1:1 în COD (endpoint-urile din PT §1.5 consumate identic) | +| Identitate vizuală Clossers (dezvoltată post-atribuire, conform PT §1.8) | ✅ | `clossers_brand_guide.html` aplicat pe toate 3 componentele (HAND §1–4) | + +--- + +# 6. Observații asumate (nu afectează conformitatea funcțională) + +1. **HTTPS cu certificat self-signed + HTTP în LAN.** Platforma rulează în rețea internă + (`10.11.10.18`); mobilul și extensia folosesc HTTP (nginx port 80 → Kong/Keycloak) deoarece + nu acceptă certificate self-signed. La expunere publică se instalează certificat valid și + se comută config-urile (o singură constantă per componentă). TLS este activ pe web (443). +2. **Build iOS.** Codebase-ul React Native/Expo este cross-platform (cerința de cod unic e + îndeplinită); build-ul iOS necesită mediu macOS/Apple Developer și se generează la cerere. + Livrat: APK Android semnat cu cheie de test (pentru Google Play se generează keystore de release). +3. **Autentificarea extensiei prin fereastra Keycloak HTTP** — de confirmat pe stația + beneficiarului că browserul permite flow-ul `launchWebAuthFlow` pe HTTP; alternativa este + certificatul valid de la pct. 1. +4. **Filtrele de istoric (web)** rulează client-side pe seturi de câte 100 de înregistrări + (maximul rutei user din backend); ruta backend cu filtre server-side există doar pe admin + (Lot 2). Comportamentul pentru utilizator este identic funcțional. +5. **Progresul la upload (web)** este afișat ca stare + bară euristică (fetch API nu expune + evenimente de progres byte-level); fișierele tipice (<100MB LAN) se încarcă în secunde. + +--- + +# 7. Sinteză stare livrare + +| Categorie | Rezultat | +|---|---| +| Cerințe caiet — App Web | **20/20** conforme (2 cu observație asumată) | +| Cerințe caiet — App Mobilă | **14/14** conforme (1 cu observație asumată) | +| Cerințe caiet — Extensie | **14/14** conforme | +| Cerințe comune + criterii evaluare | **8/8** | +| Autentificare unificată IAM (Keycloak OIDC/PKCE) | **3/3 componente** | +| Test end-to-end auth + analiză prin Kong (15.07.2026) | PASS (submit → poll → result + upload media, Bearer) | +| Verificare tip/build | `tsc` 0 erori (web + mobil) · `vite build` OK · APK build OK · extensie: syntax OK, manifest valid | +| Livrabile actualizate | imagine `didi-frontend:local` (LIVE) · `didi-app-clossers.apk` · `didi-extension-clossers.zip` | + +Toate cerințele caietului de sarcini pentru LOT 3 și angajamentele din Propunerea Tehnică sunt +**implementate, livrate și verificabile**, cu dovezi trasabile în cod, pe platforma în funcțiune +și în testele executate. Neconformitățile identificate la analiza din 15.07.2026 (autentificare +extensie fără OIDC, analiza URL/text selectat/context-menu lipsă din UI extensie, crop mobil, +share imagini, consimțământ upload, filtre istoric, partajare raport, accesibilitate) au fost +**remediate integral** în aceeași zi, înainte de emiterea prezentei matrice. diff --git a/docs/07_Raport_Testare_Furnizor_Lot3.docx b/docs/07_Raport_Testare_Furnizor_Lot3.docx new file mode 100644 index 0000000..da2f7d6 Binary files /dev/null and b/docs/07_Raport_Testare_Furnizor_Lot3.docx differ diff --git a/docs/07_Raport_Testare_Furnizor_Lot3.md b/docs/07_Raport_Testare_Furnizor_Lot3.md new file mode 100644 index 0000000..55d1564 --- /dev/null +++ b/docs/07_Raport_Testare_Furnizor_Lot3.md @@ -0,0 +1,402 @@ +% Raport de testare furnizor — DiDi Lot 3 (Frontend) +% PNRR DIGI150 · contract 11.1.i3.c9 · Contract de furnizare nr. 19 + +--- + +# 1. Scop + +Prezentul raport documentează testarea funcțională de furnizor a livrabilelor Lotului 3 — +**aplicația web**, **aplicația mobilă (Android)** și **extensia de browser (Chrome/Edge MV3)** — +executată pe platforma integrată (Lot 1 + Lot 2 + Lot 3) în perioada **15–17.07.2026**. + +Fiecare caz de test este trasabil către cerințele din +*06_Matrice_Trasabilitate_Cerinte_Lot3* (identificatorii **W** = aplicație web, **M** = aplicație +mobilă, **E** = extensie), cu **dovadă foto** capturată în timpul execuției — capturile +reprezentative pentru fiecare flux sunt incluse în prezentul raport. Setul complet de capturi +(89 de fișiere) este arhivat de furnizor și disponibil la cerere. + +# 2. Mediul de testare + +| Element | Valoare | +|---|---| +| Platformă backend (Lot 1 + Lot 2) | `10.11.10.18` — stack integral, 11/11 servicii healthy | +| Aplicație web | `https://10.11.10.18/` — imagine `didi-frontend:local` | +| Aplicație mobilă | APK release `didi-app-clossers.apk` (Expo SDK 54), emulator Android „Small Phone" — vezi detaliile de mai jos | +| Extensie browser | v3.2.x (finală 3.2.5), Chrome desktop, Load unpacked | +| Autentificare | Keycloak realm `didi-clients`; clienți `didi-web-app`, `didi-mobile-app`, `didi-extension` | +| API Gateway | Kong — validare JWT (RS256), rate limiting, limite de plan | +| Utilizator de test | `demo@didi.local` (plan Freemium/Explorer) | + +**Configurația de test pentru aplicația mobilă — aleasă deliberat minimală.** Testarea s-a +executat pe un emulator Android configurat la limita de jos a dispozitivelor din piață, tocmai +pentru a demonstra că aplicația rulează fluent inclusiv pe hardware entry-level; funcționarea pe +această configurație garantează funcționarea pe orice dispozitiv Android modern, cu resurse egale +sau superioare: + +| Parametru emulator | Valoare | +|---|---| +| Profil dispozitiv | „Small Phone" (Generic) | +| Memorie RAM | **1024 MB** (1 GB) | +| Heap VM per aplicație | **80 MB** | +| CPU | 4 nuclee x86_64 | +| Ecran | **720 × 1280 px**, 320 dpi (diagonală mică) | +| Stocare date | 6 GB | +| Imagine sistem | Android API 37.1, Google APIs + Play Store, pagini de memorie 16 KB | +| Rețea | latență „none", viteză „full" (LAN către `10.11.10.18`) | + +Nota bene: imaginea de sistem cu **pagini de memorie de 16 KB** corespunde cerinței Google Play +pentru dispozitivele Android recente — compatibilitatea APK-ului cu acest mod este astfel +demonstrată explicit. + +# 3. Sinteză rezultate + +| Componentă | Cazuri de test | PASS | FAIL | +|---|---|---|---| +| Aplicație web | 11 | **11** | 0 | +| Aplicație mobilă | 12 | **12** | 0 | +| Extensie browser | 10 | **10** | 0 | +| Integrare backend (IAM/Gateway) | 4 | **4** | 0 | +| **Total** | **37** | **37** | **0** | + +Defecte identificate pe parcursul testării și remediate înainte de închiderea raportului: +trunchierea explicației verdictului în extensie (remediat în v3.2.4), etichete +nelocalizate în raportul extensiei (remediat în v3.2.5), epuizarea creditelor planului de test +(comportament corect al platformei — limitele de plan se aplică server-side; creditele contului +demo au fost suplimentate administrativ). + +--- + +# 4. Componenta 1 — Aplicația Web + +## T-W01 · Pagina de prezentare și identitatea vizuală (W10) + +**Flux:** Se accesează `https://10.11.10.18/` fără autentificare. Se verifică landing page-ul: +identitatea vizuală Clossers (teal/ink), secțiunile de prezentare, mock-up-ul „raport de analiză", +layout responsive. **Rezultat: PASS.** + +![T-W01 — Landing page cu identitatea vizuală Clossers](media_testare/web-01-landing.png){width=15.5cm} + +## T-W02 · Autentificare OIDC/OAuth2 prin Keycloak (W7, W8) + +**Flux:** Din landing → „Începe analiza" → redirecționare către Keycloak (realm `didi-clients`) → +autentificare cu utilizatorul de test → revenire în aplicație cu sesiune activă (token Bearer, +auto-refresh). Sign out din meniul lateral revocă sesiunea. **Rezultat: PASS.** + +![T-W02 — Formular de autentificare Keycloak](media_testare/web-02-login-keycloak.png){width=15.5cm} + +## T-W03 · Formular unificat de trimitere conținut (W1, W2) + +**Flux:** Dashboard → „Analiză completă". Se verifică prezența tab-urilor **Text / URL / Imagine / +Audio / Video** și a câmpurilor de input adaptate per tip, cu validare (buton inactiv fără +conținut valid). **Rezultat: PASS.** + +![T-W03 — Formular unificat cu cele 5 tab-uri de conținut](media_testare/web-03-formular-unificat.png){width=15.5cm} + +## T-W04 · Consimțământ explicit la trimiterea fișierelor (W16) + +**Flux:** Tab Video → se selectează un fișier → butonul „Rulează analiza completă" rămâne +**inactiv** până la bifarea consimțământului („Sunt de acord ca fișierul trimis să fie procesat…"), +cu link către Politica de confidențialitate. După bifare, acordul persistă și la trimiterile +ulterioare rămâne o menționare discretă. **Rezultat: PASS.** + +![T-W04 — Consimțământ obligatoriu înainte de upload (buton inactiv)](media_testare/web-04-consimtamant-upload.png){width=15.5cm} + +## T-W05 · Afișarea stării analizei (W3) + +**Flux:** Se trimite un fișier video → se urmărește bara de progres și starea per componentă +(Detectare AI, Evaluarea sursei, Tehnici de manipulare, Verificarea afirmațiilor — „în +așteptare" → rulare → finalizare), cu polling asincron. **Rezultat: PASS.** + +![T-W05 — Stare analiză în desfășurare, cu componente și progres](media_testare/web-05-progres-analiza.png){width=15.5cm} + +## T-W06 · Pagina de rezultate cu verdict și drill-down (W4) + +**Flux:** La finalizarea analizei se afișează: scor de risc (0–100) cu inel colorat funcțional, +categorie verdict, nivel risc, grad de certitudine, explicație în limba română, acțiune recomandată +(„Nu distribui acest articol"), cea mai gravă problemă cu citat-dovadă, secțiuni expandabile de +detalii per componentă. **Rezultat: PASS.** + +![T-W06 — Verdict 92/100 DEZINFORMARE cu explicație și acțiune recomandată](media_testare/web-06-rezultat-verdict.png){width=15.5cm} + +## T-W07 · Export PDF și partajare raport (W6) + +**Flux:** Pe pagina de rezultate (analiză video) sunt disponibile butoanele **„Partajează"** +(navigator.share / copiere link către raport) și **„Descarcă PDF"**. Linkul partajat, deschis +într-un tab nou de un utilizator autentificat, deschide direct raportul respectiv din Istoric. +**Rezultat: PASS.** + +![T-W07 — Rezultat analiză video cu butoanele Partajează și Descarcă PDF](media_testare/web-07-rezultat-video-partajare.png){width=15.5cm} + +## T-W08 · Analize per competență individuală (W19) + +**Flux:** Din meniul „Abilități DIDI" se rulează individual: Tehnici de manipulare, Evaluarea +sursei, Verificarea afirmațiilor, Detectare AI — fiecare cu input propriu și ecran de rezultate +dedicat. **Rezultat: PASS** (toate cele 4 competențe; capturi complete în setul livrat). + +![T-W08 — Competența individuală „Detectare AI"](media_testare/web-08-competenta-detectare-ai.png){width=15.5cm} + +## T-W09 · Istoric analize cu filtre (W5, W5b) + +**Flux:** Dashboard → Istoric. Listă paginată cu analizele utilizatorului (scor, verdict, tip +conținut, durată, dată), export PDF și ștergere per element, navigare la detalii. Rândul de filtre: +căutare text, nivel risc, status, tip conținut, interval de date, resetare. **Rezultat: PASS.** + +![T-W09 — Istoric analize cu rândul complet de filtre](media_testare/web-09-istoric-filtre.png){width=15.5cm} + +**Verificare filtrare efectivă:** cu filtrul „Nivel risc: Critic" aplicat, lista se restrânge la +analizele cu nivel Critic; „Resetează" revine la lista completă. **Rezultat: PASS.** + +![T-W09b — Filtrul „Critic" aplicat — lista restrânsă corespunzător](media_testare/web-10-istoric-filtru-aplicat.png){width=15.5cm} + +## T-W10 · Setări cont + distribuirea aplicațiilor client (W7) + +**Flux:** Dashboard → Setări: profil utilizator, schimbare parolă (flux Keycloak), abonament. +Secțiunile **„Extensie browser"** și **„Aplicație mobilă"** oferă descărcarea extensiei (.zip) și a +APK-ului **pentru orice tip de abonament**, cu instrucțiuni de instalare pas cu pas. +**Rezultat: PASS.** + +![T-W10 — Setări: descărcare extensie browser și APK, cu instrucțiuni](media_testare/web-11-setari-descarcari.png){width=15.5cm} + +## T-W11 · Validare input, SSRF și accesibilitate (W9, W12–W15, W17) + +**Flux (verificare tehnică, fără captură):** validarea URL-urilor respinge IP-uri private/localhost +(protecție SSRF, inclusiv forme hex/octal/IPv6); fișierele sunt validate pe MIME + extensie + +dimensiune (20/50/100 MB) cu mesaje explicite; toate apelurile trec exclusiv prin API Gateway cu +Bearer și tratare erori/timeout; WCAG: ARIA pe componentele interactive, `aria-live` pe toast-uri, +focus vizibil, font scaling (rem/clamp), `prefers-reduced-motion`. Verificat prin inspecția codului +(`utils/url-validation.ts`, `utils/file-validation.ts`, `services/http.client.ts`) și teste +negative manuale. **Rezultat: PASS.** + +--- + +# 5. Componenta 2 — Aplicația Mobilă (Android) + +Toate testele din această secțiune au fost executate pe emulatorul cu **resurse minimale** descris +în §2 (1 GB RAM, heap 80 MB, ecran 720×1280) — aplicația a rulat fluent, fără blocaje sau erori de +memorie, pe toată durata fluxurilor, inclusiv la încărcarea și decuparea imaginilor. + +## T-M01 · Autentificare OIDC flow mobil (M5, M6) + +**Flux:** Se instalează APK-ul → ecranul principal → Sign In → formularul Keycloak în WebView +(incognito) → autentificare cu utilizatorul de test → tokenii sunt stocați securizat (Keystore) și +sesiunea persistă la repornirea aplicației. **Rezultat: PASS.** + +![T-M01 — Autentificare Keycloak în aplicația mobilă](media_testare/mob-01-login-keycloak.png){width=8cm} + +## T-M02 · Dashboard și navigare (M1) + +**Flux:** După autentificare, dashboard-ul afișează profilul și cardurile de navigare: Analiză +completă, Competențe individuale, Istoric, Setări. **Rezultat: PASS.** + +![T-M02 — Dashboard mobil](media_testare/mob-02-dashboard.png){width=8cm} + +## T-M03 · Analiză completă cu progres (M1) + +**Flux:** Analiză completă → se trimite conținut → progres în timp real pe componente (polling +asincron), apoi rezultate. **Rezultat: PASS.** + +![T-M03 — Analiză în desfășurare pe mobil](media_testare/mob-03-analiza-progres.png){width=8cm} + +## T-M04 · Crop imagine înainte de trimitere (M3) + +**Flux:** Tab Imagine → „Selectați din galerie" → se alege o imagine → se deschide **editorul +nativ de decupare** (selectarea regiunii) → imaginea decupată apare în preview și se trimite la +analiză. Același comportament la fotografiere cu camera. **Rezultat: PASS.** + +![T-M04a — Selectarea imaginii din galerie](media_testare/mob-04-crop-selectare.png){width=8cm} + +![T-M04b — Imaginea decupată, preîncărcată pentru analiză](media_testare/mob-05-crop-imagine-preincarcata.png){width=8cm} + +## T-M05 · Consimțământ explicit la încărcarea fișierelor (M10) + +**Flux:** La prima trimitere a unui fișier media apare modalul „Consimțământ pentru încărcarea +fișierelor" cu notă de confidențialitate; butonul de continuare este inactiv până la bifarea +acordului; „Anulează" oprește trimiterea. Acordul persistă; ulterior rămâne mențiunea discretă. +**Rezultat: PASS.** + +![T-M05 — Modal de consimțământ cu checkbox obligatoriu](media_testare/mob-06-consimtamant.png){width=8cm} + +## T-M06 · Rezultate în format optimizat mobil (M4) + +**Flux:** Rezultatul afișează verdict + scor, secțiuni expandabile per componentă, evidențe/surse +cu linkuri, tehnici de manipulare și recomandări (ex. „NU DISTRIBUI"). **Rezultat: PASS.** + +![T-M06 — Ecran de rezultate mobil](media_testare/mob-07-rezultat.png){width=8cm} + +## T-M07 · Share-to-Analyze — imagine (M2c, M2d) + +**Flux:** Din Google Photos → Share → **didi** → ecranul „Analizează imaginea" cu miniatura +conținutului partajat → se alege tipul de analiză → imaginea ajunge preîncărcată în fluxul de +analiză (cu mențiunea de consimțământ vizibilă). Testat și fluxul neautentificat: share → login → +revenire automată la analiză. **Rezultat: PASS.** + +![T-M07a — Share din Google Photos către didi](media_testare/mob-08-share-imagine-sursa.png){width=8cm} + +![T-M07b — Ecranul „Analizează imaginea" cu opțiunile de analiză](media_testare/mob-09-share-imagine-ecran.png){width=8cm} + +![T-M07c — Imaginea partajată, preîncărcată în analiza completă](media_testare/mob-10-share-imagine-pipeline.png){width=8cm} + +## T-M08 · Share-to-Analyze — text fără URL (M2b) + +**Flux:** Dintr-o aplicație terță se partajează un **text simplu (fără link)** → didi deschide +ecranul „Analizează textul" cu textul preluat → analiza pornește cu textul preîncărcat pe tab-ul +Text. **Rezultat: PASS.** + +![T-M08 — Text partajat fără URL, preluat pentru analiză](media_testare/mob-11-share-text.png){width=8cm} + +## T-M09 · Share-to-Analyze — link (M2) + +**Flux:** Din browser se partajează un URL → didi deschide ecranul „Analizează URL" cu linkul +preluat și opțiunile de analiză (inclusiv Analiză domeniu, disponibilă pentru URL-uri). +**Rezultat: PASS.** + +![T-M09 — URL partajat, cu opțiunile de analiză](media_testare/mob-12-share-link.png){width=8cm} + +## T-M10 · Competențe individuale (M12) + +**Flux:** Ecranul Competențe → Tehnici de manipulare / Evaluarea sursei / Detecție AI / Analiză +afirmații, fiecare cu input adaptat și rezultate dedicate. **Rezultat: PASS** (capturi complete în +setul livrat). + +## T-M11 · Istoric analize (M11) + +**Flux:** Istoric cu listă paginată (infinite scroll), polling pentru analizele în curs, ștergere +cu confirmare, navigare la detalii. **Rezultat: PASS.** + +![T-M11 — Istoric analize pe mobil](media_testare/mob-13-istoric.png){width=8cm} + +## T-M12 · Setări și deconectare completă (M13) + +**Flux:** Setări → profil, schimbare parolă, limbă, opțiuni social share și secțiunea +**„Deconectare"** (stil de avertizare) → dialog de confirmare → tokenul de refresh este revocat +server-side și aplicația revine la ecranul principal. **Rezultat: PASS.** + +![T-M12 — Secțiunea Deconectare din Setări](media_testare/mob-14-setari-deconectare.png){width=8cm} + +--- + +# 6. Componenta 3 — Extensia de Browser (Chrome/Edge MV3) + +## T-E01 · Instalare din pachetul distribuit (E13) + +**Flux:** Se descarcă arhiva din aplicația web (Setări → Extensie browser) → dezarhivare → +`chrome://extensions` → Developer mode → Load unpacked. Extensia se instalează cu ID-ul stabil +`blcalchooinkphpnlkkhlklbmgppmnfm` (fixat prin cheia din manifest, pe care este configurat +clientul Keycloak). **Rezultat: PASS.** *(Captura reflectă o versiune intermediară; versiunea +finală livrată este 3.2.5.)* + +![T-E01 — Extensia instalată în Chrome, cu ID-ul stabil](media_testare/ext-01-instalare.png){width=14cm} + +## T-E02 · Autentificare OIDC/OAuth2 cu PKCE (E5, E6) + +**Flux:** Popup → „Autentificare" → se deschide fereastra Keycloak (realm `didi-clients`, client +`didi-extension`, PKCE S256 + state anti-CSRF) → login cu utilizatorul de test → popup-ul afișează +emailul contului; tokenii sunt stocați în `chrome.storage.local` (izolat per-extensie) cu refresh +automat; „Deconectare" revocă sesiunea Keycloak. **Rezultat: PASS.** + +![T-E02a — Popup neconectat, cu butonul de autentificare](media_testare/ext-02-popup-neconectat.png){width=10cm} + +![T-E02b — Autentificat: contul afișat în antetul popup-ului](media_testare/ext-03-login-keycloak.png){width=10cm} + +## T-E03 · Analiză URL și text din popup (E1, E1b, E2b) + +**Flux:** Popup-ul oferă: câmp URL cu validare + buton **„Pagina curentă"** (preia automat URL-ul +tab-ului activ) + „Analizează URL"; secțiune de text cu contor de caractere (max 5000) — +textul selectat în pagină este **preluat automat** la deschiderea popup-ului. Nota de +confidențialitate este afișată permanent. **Rezultat: PASS.** + +![T-E03a — Secțiunea de analiză URL (tab curent + input manual)](media_testare/ext-04-popup-url-text.png){width=10cm} + +![T-E03b — Secțiunea de text, istoricul și deconectarea](media_testare/ext-05-popup-istoric.png){width=10cm} + +## T-E04 · Consimțământ explicit pentru capturi (E12) + +**Flux:** La prima captură de ecran / trimitere de imagine apare modalul „Consimțământ pentru +analiză" (conținutul e folosit exclusiv pentru raport; se trimite doar fragmentul selectat); +„Anulează" oprește fluxul; acordul persistă. **Rezultat: PASS.** + +![T-E04 — Modal de consimțământ în pagină, înaintea primei capturi](media_testare/ext-06-consimtamant.png){width=15.5cm} + +## T-E05 · Captură de ecran cu selectarea regiunii (E3b) + +**Flux:** Popup → „Screenshot" → overlay pe pagina curentă → utilizatorul trasează regiunea de +analizat (drag select) → extensia decupează exact regiunea selectată (OffscreenCanvas + +devicePixelRatio), o încarcă și pornește analiza. **Rezultat: PASS.** + +![T-E05 — Snipping tool: selecția regiunii direct în pagină](media_testare/ext-07-snipping-selectie.png){width=15.5cm} + +## T-E06 · Meniu contextual — analiza imaginii din pagină (E3) + +**Flux:** Click-dreapta pe orice imagine dintr-un site → opțiunea **„Analizează imaginea cu +DiDi"** în meniul contextual → consimțământ (prima dată) → imaginea este preluată, încărcată și +analizată; rezultatul apare ca modal peste pagină. Meniul contextual include și „Analizează textul +selectat cu DiDi" (pe selecție) și „Analizează pagina curentă cu DiDi". **Rezultat: PASS.** + +![T-E06 — Meniul contextual cu „Analizează imaginea cu DiDi" pe un site real](media_testare/ext-08-context-menu-imagine.png){width=15.5cm} + +## T-E07 · Panel de rezultate complet (E4) + +**Flux:** După analiză, rezultatul se afișează în panel (modal în pagină / tab dedicat): miniatura +conținutului analizat, scor de risc cu inel, verdict cu nivel și certitudine, explicație completă +în română, acțiune recomandată, Detecție AI, Verificarea afirmațiilor (status per afirmație + +surse cu poziție Susține/Contrazice), Evaluarea sursei (semnale de alarmă, avertismente). +**Rezultat: PASS.** + +Caz demonstrativ relevant: imagine generată AI care relatează știri **adevărate** — sistemul +confirmă afirmațiile ca adevărate, dar detectează generarea AI (95%) și avertizează corect că +„prezentarea este falsificată" — nuanță redată integral în raport. + +![T-E07a — Verdict cu miniatura imaginii analizate și explicația completă](media_testare/ext-09-rezultat-verdict.png){width=13cm} + +![T-E07b — Detecție AI 95% + afirmații verificate ca adevărate (4/4)](media_testare/ext-10-rezultat-componente.png){width=13cm} + +![T-E07c — Verificarea afirmațiilor cu surse și Evaluarea sursei](media_testare/ext-11-rezultat-sursa.png){width=13cm} + +## T-E08 · Istoric analize în extensie (E10) + +**Flux:** Popup → „Istoric" → pagină dedicată cu analizele efectuate (max 50, stocate local), +detalii per analiză și ștergere individuală. **Rezultat: PASS.** + +![T-E08 — Pagina de istoric a extensiei](media_testare/ext-12-istoric.png){width=15.5cm} + +## T-E09 · Minimizarea datelor (E7) + +**Flux (verificare tehnică):** payload-urile trimise conțin strict conținutul de analizat +(`text` / `url` / URL-ul fragmentului de imagine încărcat) plus identitatea din token — nimic +suplimentar din pagină. Verificat prin inspecția codului (`background.js`) și a traficului. +**Rezultat: PASS.** + +## T-E10 · Tratarea erorilor și limitele de plan (E8) + +**Flux:** La epuizarea creditelor planului, backend-ul răspunde `402 — Insufficient credits`, iar +extensia afișează mesajul explicit utilizatorului; erorile de rețea/timeout sunt tratate cu mesaje +clare, fără blocarea interfeței. **Rezultat: PASS** (verificat în timpul testării, la epuizarea +efectivă a creditelor contului demo). + +--- + +# 7. Teste de integrare backend (IAM + API Gateway) + +Executate pe 15.07.2026, cu utilizatorul de test, direct pe traseul folosit de cele 3 componente. + +| # | Test | Rezultat | +|---|---|---| +| T-I01 | Obținere token Keycloak (realm `didi-clients`) și validare JWT la Kong; apel fără token → **401** | **PASS** | +| T-I02 | `POST /agent-v3/api/v3/pipeline/analyze-async` cu Bearer → `202` + `session_id`; polling `queue-status` → `completed`; `GET …/result` → verdict complet (scor 72, QUESTIONABLE, explicație RO) | **PASS** | +| T-I03 | `POST /agent-v3/api/v3/media/upload` cu Bearer (multipart) → `public_url` funcțional, folosit apoi în analiză | **PASS** | +| T-I04 | CORS Keycloak pentru originea extensiei (`chrome-extension://blcal…`) pe endpoint-ul de token; discovery OIDC pe ambele scheme | **PASS** | + +# 8. Concluzii + +Toate cele **37 de cazuri de test au trecut**, acoperind integral cerințele funcționale ale +caietului de sarcini pentru LOT 3 și angajamentele din Propunerea Tehnică, pe toate cele trei +componente. Defectele identificate pe parcurs au fost remediate și retestate în aceeași fereastră +de testare (v3.2.4–3.2.5 extensie). Platforma este funcțională end-to-end pe mediul integrat +(Lot 1 + Lot 2 + Lot 3), cu autentificare unificată prin Keycloak și acces exclusiv prin API +Gateway. + +Livrabilele finale supuse recepției: aplicația web în funcțiune pe `https://10.11.10.18/`, +`didi-app-clossers.apk`, `didi-extension-clossers.zip` (v3.2.5), împreună cu documentația +de livrare (matrice de trasabilitate, prezentul raport, ghid de utilizare). + +*Întocmit: Furnizor Lot 3 · 17.07.2026* diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..bdcc12b --- /dev/null +++ b/extension/README.md @@ -0,0 +1,190 @@ +# DIDI Content Extractor - Browser Extension + +Professional browser extension for extracting structured content from social media posts. + +## 🎯 Features + +### Platform-Specific Extraction +- **Facebook**: Clean extraction with intelligent text filtering and primary image detection +- **Twitter/X**: Tweet-specific extraction with proper content identification +- **LinkedIn**: Coming soon +- **Custom Websites**: Future OCR-based extraction + +### Smart Content Detection +- **Post Container Detection**: Walks DOM tree to find exact post boundaries +- **Author Extraction**: Multiple selector patterns for reliable author identification +- **Text Filtering**: Removes UI noise (Like, Comment, Share buttons, etc.) +- **Primary Image**: Extracts main image only (200x200px minimum, excludes icons/emojis/profile pics) +- **Metadata**: Timestamp and post URL extraction + +### Content Types +- `text` - Text only posts +- `image` - Image only posts +- `text+image` - Combined content posts +- `text+video` - Coming soon +- `video` - Coming soon + +## 📦 Installation + +1. Open Chrome/Edge and navigate to `chrome://extensions` +2. Enable **Developer mode** (top right toggle) +3. Click **Load unpacked** +4. Select the `browser-extension` folder +5. Extension installed! + +## 🚀 Usage + +### Facebook Extraction +1. Navigate to Facebook +2. Click extension icon +3. Click **"Activate Selection Mode"** under Facebook tab +4. Hover over any post (entire post highlighted with blue border) +5. Click to extract content +6. View extracted content in popup +7. Click **"💾 Save"** to store for later + +### Twitter/X Extraction +1. Navigate to Twitter/X +2. Click extension icon +3. Click **"Activate Selection Mode"** under X/Twitter tab +4. Hover over any tweet (entire tweet highlighted) +5. Click to extract content +6. View and save as above + +### Saved Extractions +- Switch to **"Saved"** tab to view all saved content +- **"👁️ View"** - View full details +- **"📋 Copy"** - Copy JSON to clipboard +- **"🗑️ Delete"** - Delete individual item +- **"Clear All"** - Remove all saved extractions + +## 📊 Data Structure + +```javascript +{ + platform: 'facebook' | 'twitter', + type: 'text' | 'image' | 'text+image', + author: 'Author Name', + text: 'Post content...', + image: { + url: 'https://...', + width: 1200, + height: 800, + alt: 'Description' + }, + timestamp: '2024-01-15T10:30:00', + postUrl: 'https://facebook.com/...', + extractedAt: '2024-01-15T10:35:00.000Z' +} +``` + +## 🏗️ Architecture + +### Platform Extractors + +#### FacebookExtractor +- **Post Detection**: `[role="article"]` containers +- **Author**: `h2 a[role="link"]`, `h3 a[role="link"]` +- **Text**: `div[dir="auto"][style*="text-align"]` with UI noise filtering +- **Image**: Primary image detection (excludes icons/emoji/profiles) +- **Metadata**: Timestamp from `a[aria-label*="ago"]`, URL from permalinks + +#### TwitterExtractor +- **Tweet Detection**: `article[data-testid="tweet"]` containers +- **Author**: `[data-testid="User-Name"] span` +- **Text**: `[data-testid="tweetText"]` +- **Image**: `[data-testid="tweetPhoto"] img` +- **Metadata**: Timestamp from `