diff --git a/.agents/skills/frontend-designer.md b/.agents/skills/frontend-designer.md deleted file mode 100644 index afcbfd4..0000000 --- a/.agents/skills/frontend-designer.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: frontend-design -description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics. -license: Complete terms in LICENSE.txt ---- - -This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. - -The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. - -## Design Thinking - -Before coding, understand the context and commit to a BOLD aesthetic direction: -- **Purpose**: What problem does this interface solve? Who uses it? -- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. -- **Constraints**: Technical requirements (framework, performance, accessibility). -- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? - -**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. - -Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: -- Production-grade and functional -- Visually striking and memorable -- Cohesive with a clear aesthetic point-of-view -- Meticulously refined in every detail - -## Frontend Aesthetics Guidelines - -Focus on: -- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. -- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. -- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. -- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. -- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. - -NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. - -Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. - -**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. - -Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/.crush.json b/.crush.json deleted file mode 100644 index 2ca2fe9..0000000 --- a/.crush.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://charm.land/crush.json", - "permissions": { - "allowed_tools": [ - "view", - "ls", - "grep", - "glob", - "edit", - "go", - "just", - "agent", - "agentic_fetch" - ] - }, - "options": { - "initialize_as": "AGENTS.md", - "skills_paths": ["./.agents/skills"] - }, - "lsp": { - "typescript": { - "command": "typescript-language-server", - "args": ["--stdio"] - }, - "omnisharp": { - "command": "wsl", - "args": ["--", "bash", "-lc", "\"OmniSharp\""] - } - } -} diff --git a/.gitignore b/.gitignore index d914c32..29a138d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,23 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ +# .NET +bin/ +obj/ dist/ -web-build/ -expo-env.d.ts +*.dll +*.exe +*.pdb -# Native -.kotlin/ -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision +# IDE +.vs/ +.vscode/ +.idea/ +*.user +*.suo -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS +# OS .DS_Store -*.pem +Thumbs.db -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -# generated native folders -/ios -/android +# Project specific +*.csv +*.dlens +*.apk diff --git a/.just/emu.just b/.just/emu.just deleted file mode 100644 index 7152410..0000000 --- a/.just/emu.just +++ /dev/null @@ -1,35 +0,0 @@ -set shell := ["C:/Program Files/Git/usr/bin/bash.exe", "-c"] -set unstable := true - -android_sdk := replace(env('LOCALAPPDATA'), '\', '/') / "Android/Sdk" -adb := android_sdk / "platform-tools/adb.exe" -avd := android_sdk / "cmdline-tools/latest/bin/avdmanager.bat" -emulator := android_sdk / "emulator/emulator.exe" -camera_virtual := "-camera-back virtualscene -virtualscene-poster wall=\"" + (justfile_directory() / "TestImages/reference_alpha/serra_angel.jpg") + "\"" -camera_webcam := "-camera-back webcam0 -camera-front webcam0" - -default camera="virtual": - {{ emulator }} -avd Pixel_6 {{ if camera == "virtual" { camera_virtual } else { camera_webcam } }} -no-snapshot-load -gpu host - -install: - {{ avd }} delete avd -n Pixel_6 - {{ avd }} create avd -n Pixel_6 -k "system-images;android-36;google_apis_playstore;x86_64" -d pixel_6 - -# Wait for emulator to fully boot (timeout after 2 minutes) -[script] -emu-wait: - # Wait for Android emulator to boot with timeout - TIMEOUT=120 - - echo "Waiting for emulator to boot..." - - for ((i=TIMEOUT; i>0; i--)); do - if [ "$({{ adb }} shell getprop sys.boot_completed 2>/dev/null)" = "1" ]; then - echo "Emulator ready" - exit 0 - fi - sleep 1 - done - - echo "Emulator failed to boot within 2 minutes" - exit 1 diff --git a/.justfile b/.justfile index df476fa..e62b769 100644 --- a/.justfile +++ b/.justfile @@ -1,80 +1,19 @@ -# Scry development commands +# Scry build recipes -set shell := ["C:/Program Files/Git/usr/bin/bash.exe", "-c"] -set unstable := true +# Default recipe - show available commands +default: + @just --list -mod emu '.just/emu.just' +# Build both standard and embedded versions for all platforms +build apk="delver.apk": + rm -rf dist + dotnet publish -c Release -r win-x64 -o dist/win-x64/standard + dotnet publish -c Release -r win-x64 -p:EmbeddedApk={{apk}} -o dist/win-x64/embedded + dotnet publish -c Release -r linux-x64 -o dist/linux-x64/standard + dotnet publish -c Release -r linux-x64 -p:EmbeddedApk={{apk}} -o dist/linux-x64/embedded + dotnet publish -c Release -r osx-x64 -o dist/osx-x64/standard + dotnet publish -c Release -r osx-x64 -p:EmbeddedApk={{apk}} -o dist/osx-x64/embedded -# Android SDK paths - -android_sdk := replace(env('LOCALAPPDATA'), '\', '/') / "Android/Sdk" -adb := android_sdk / "platform-tools/adb.exe" - -[private] -@default: - just --list - -# Build a project -build project="src/Scry.App" target="net10.0-android": - @echo "Building {{ project }}..." - dotnet build {{ project }} -f {{ target }} -c Debug - @echo "Build complete" - -# Publish a project (creates distributable) -publish project="src/Scry.App" target="net10.0-android": - @echo "Publishing {{ project }} (this takes a while)..." - dotnet publish {{ project }} -f {{ target }} -c Release - @echo "Publish complete" - -# Install APK to emulator/device -install: - {{ adb }} install -r src/Scry.App/bin/Release/net10.0-android/publish/land.charm.scry-Signed.apk - -# Launch the app on emulator/device -launch: - {{ adb }} shell am start -n land.charm.scry/crc64fb23cc0d511b0157.MainActivity - -# Publish, install, and launch -run: (publish "src/Scry.App") install launch - -# View app crash logs -logs: - {{ adb }} logcat -d | grep -iE "land.charm.scry|scry|mono|dotnet" | tail -80 - -# Run tests -test: - dotnet test test/Scry.Tests - -# Generate the card hash database from Scryfall -gen-db: (build "tools/DbGenerator" "net10.0") - @echo "Running Database generator (this takes a while)..." - dotnet run --project tools/DbGenerator --no-build -- src/Scry.App/Resources/Raw/card_hashes.db - @echo "Completed generating the database" - -# Start Expo dev server with Convex (hot reload) -dev: - bun run dev - -# Start just the Expo dev server -start: - bun run dev:expo - -# Start just Convex dev server -convex-dev: - bun run dev:convex - -# Run Expo app on Android emulator -android: - bun run android - -# Install Expo app dependencies -expo-install: - bun install - -# Run hash migration to Convex -expo-migrate: - bun run migrate - -# TypeScript check for Expo app -expo-typecheck: - bun run typecheck +# Clean build artifacts +clean: + rm -rf bin obj dist diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 78b4cc7..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,288 +0,0 @@ -# Agent Instructions - -## Overview - -Scry is a Magic: The Gathering card scanner app built with Expo (React Native) and Convex. It uses perceptual hashing to match photographed cards against a database of known card images from Scryfall. - -**Tech Stack:** -- **Frontend**: Expo/React Native with Expo Router (file-based routing) -- **Backend**: Convex (serverless functions + real-time database) -- **Image Processing**: React Native Skia, fast-opencv -- **Camera**: Adaptive (expo-camera in dev, react-native-vision-camera in production) -- **Auth**: Convex Auth with Zitadel OIDC (GDPR-compliant, no PII stored) -- **Package Manager**: Bun (not npm/yarn) - -## Build Commands - -Use `just` commands (defined in `.justfile`): - -| Task | Command | Notes | -|------|---------|-------| -| Start dev server | `just dev` | Runs Convex + Expo together | -| Expo only | `just start` | Just the Expo dev server | -| Convex only | `just convex-dev` | Just the Convex dev server | -| Run on Android | `just android` | Starts Android emulator | -| Install deps | `just expo-install` | Runs `bun install` | -| Migrate hashes | `just expo-migrate` | Migrate card hashes to Convex | -| Type check | `just expo-typecheck` | TypeScript check | -| Start emulator | `just emu` | Virtual camera (submodule) | - -### Direct Bun Commands - -```bash -bun install # Install dependencies -bun run dev # Convex + Expo hot reload -bun run dev:convex # Convex dev server only -bun run dev:expo # Expo dev server only -bun run android # Run on Android -bun run migrate # Migrate hashes to Convex -bun run typecheck # TypeScript check -bunx convex dev # Convex CLI -``` - -## Project Structure - -``` -app/ # Expo Router pages -├── _layout.tsx # Root layout (Convex + HashCache providers) -├── modal.tsx # Card details modal -├── +not-found.tsx # 404 page -└── (tabs)/ # Tab navigation group - ├── _layout.tsx # Tab bar layout - ├── index.tsx # Collection tab (home) - ├── scan.tsx # Camera scan tab - └── settings.tsx # Settings tab - -components/ -├── camera/ # Adaptive camera system -│ ├── index.tsx # AdaptiveCamera wrapper -│ ├── ExpoCamera.tsx # expo-camera (Expo Go) -│ └── VisionCamera.tsx # react-native-vision-camera (production) -└── *.tsx # Shared UI components - -convex/ # Backend (Convex functions + schema) -├── schema.ts # Database schema -├── auth.ts # Zitadel OIDC configuration -├── http.ts # HTTP endpoints for auth -├── cards.ts # Card queries/mutations -├── collections.ts # User collection functions -├── users.ts # User functions -├── scanHistory.ts # Scan history functions -└── _generated/ # Auto-generated types - -lib/ -├── recognition/ # Card recognition pipeline -│ ├── recognitionService.ts # Main recognition logic -│ ├── cardDetector.ts # Edge detection, find card quad -│ ├── perspectiveCorrection.ts # Warp to rectangle -│ ├── clahe.ts # CLAHE lighting normalization -│ ├── perceptualHash.ts # 192-bit color hash (24 bytes) -│ ├── imageUtils.ts # Resize, rotate, grayscale -│ ├── imageLoader.ts # Load/resize images -│ └── skiaDecoder.ts # Decode images with Skia -├── hooks/ # React hooks -│ ├── useAuth.ts # OAuth flow with expo-auth-session -│ ├── useCamera.ts # Adaptive camera permissions -│ ├── useConvex.ts # Convex data hooks -│ ├── useSync.ts # Hash sync hook -│ └── useUserProfile.ts # Fetch profile from Zitadel -├── context/ # React contexts -│ └── HashCacheContext.tsx # In-memory hash cache -└── db/ # Local database utilities - ├── localDatabase.ts # SQLite wrapper - └── syncService.ts # Sync with Convex - -scripts/ -└── migrate-hashes.ts # Migration script - -TestImages/ # Test images (225 files) -``` - -## Architecture - -### Recognition Pipeline - -``` -Camera Image - │ - ▼ -┌─────────────────────┐ -│ loadImageAsBase64 │ ← Resize to 480×640 -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ decodeImageBase64 │ ← Skia decodes to RGBA pixels -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ detectCard │ ← Edge detection, find card quad -│ (optional) │ -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ warpPerspective │ ← Warp detected quad to rectangle -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ applyCLAHE │ ← Lighting normalization -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ computeColorHash │ ← Compute 192-bit color hash (24 bytes) -└─────────────────────┘ - │ - ▼ -┌─────────────────────┐ -│ recognizeCard │ ← Hamming distance match against cache -└─────────────────────┘ -``` - -### Data Model (Convex Schema) - -| Table | Purpose | -|-------|---------| -| `users` | Minimal auth (no PII, GDPR compliant) | -| `cards` | Card printings with 24-byte perceptual hashes | -| `oracles` | Abstract game cards (one per unique card name) | -| `sets` | MTG sets with metadata | -| `collections` | User card collections | -| `scanHistory` | Scan history with confidence scores | -| `metadata` | Sync metadata | - -### GDPR Compliance - -- Database stores **no user PII** - only auth subject ID -- User profile (name, email, image) fetched from Zitadel userinfo endpoint on demand -- Profile held in memory only, never persisted - -### Adaptive Camera System - -The app detects its runtime environment and uses the appropriate camera: - -- **Expo Go** (`Constants.appOwnership === "expo"`): Uses `expo-camera` -- **Production builds**: Uses `react-native-vision-camera` - -Both expose the same `CameraHandle` interface with `takePhoto()`. - -## Key Algorithms - -### Perceptual Hash (pHash) - -Color-aware 192-bit hash: -1. Resize to 32×32 -2. For each RGB channel: - - Compute 2D DCT - - Extract 8×8 low-frequency coefficients (skip DC) - - Compare each to median → 63 bits per channel -3. Concatenate R, G, B hashes → 24 bytes (192 bits) - -Matching uses Hamming distance with threshold ≤25 bits and minimum confidence 85%. - -### CLAHE (Contrast Limited Adaptive Histogram Equalization) - -Applied in LAB color space to L channel only: -- Tile-based histogram equalization (8×8 tiles) -- Clip limit prevents over-amplification of noise -- Bilinear interpolation between tiles for smooth output - -## Code Conventions - -### General - -- **TypeScript**: Strict mode enabled -- **Formatting**: Prettier defaults -- **Package Manager**: Bun (never use npm/yarn) - -### React/React Native - -- Functional components with hooks -- Expo Router for navigation (file-based) -- Convex hooks for data (`useQuery`, `useMutation`) - -### Naming - -- Hooks: `useCardHashes`, `useCameraPermission` -- Components: PascalCase (`AdaptiveCamera`, `ScanScreen`) -- Files: camelCase for modules, PascalCase for components -- Convex functions: camelCase (`cards.ts`, `getByScryfallId`) - -### Convex Backend - -- Queries are reactive and cached -- Mutations are transactional -- Use `v.` validators for all arguments -- Index frequently queried fields - -## Environment Variables - -### Convex Backend (`convex/.env.local`) - -```bash -AUTH_ZITADEL_ID=your-client-id -AUTH_ZITADEL_SECRET=your-client-secret -AUTH_ZITADEL_ISSUER=https://your-zitadel-instance -``` - -### Client Side (`.env`) - -```bash -EXPO_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud -EXPO_PUBLIC_ZITADEL_ISSUER=https://your-zitadel-instance -``` - -## Common Tasks - -### Adding a New Convex Function - -1. Add function to appropriate file in `convex/` (e.g., `cards.ts`) -2. Run `bunx convex dev` to regenerate types -3. Import from `convex/_generated/api` in client code - -### Testing Recognition - -1. Add test images to `TestImages/` -2. Use the scan tab in the app -3. Check console logs for `[Scry]` prefixed messages - -### Debugging Camera Issues - -- In Expo Go: Uses `expo-camera`, check for "Dev mode" indicator -- In production: Uses Vision Camera, requires EAS build - -## Dependencies - -### Core - -- `expo` ~54.0.33 -- `expo-router` ~6.0.23 -- `convex` ^1.31.7 -- `@convex-dev/auth` ^0.0.90 -- `react` 19.1.0 -- `react-native` 0.81.5 - -### Image Processing - -- `@shopify/react-native-skia` ^2.4.18 -- `react-native-fast-opencv` ^0.4.7 - -### Camera - -- `expo-camera` ^17.0.10 (Expo Go) -- `react-native-vision-camera` ^4.7.3 (production) - -### Auth - -- `expo-auth-session` ^7.0.10 -- `expo-secure-store` ^15.0.8 - -## External Resources - -- [Scryfall API](https://scryfall.com/docs/api) - Card data source -- [Convex Docs](https://docs.convex.dev/) - Backend documentation -- [Expo Router](https://docs.expo.dev/router/introduction/) - Navigation -- [docs/CARD_RECOGNITION.md](docs/CARD_RECOGNITION.md) - Recognition architecture diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..06ed5ea --- /dev/null +++ b/Program.cs @@ -0,0 +1,369 @@ +using System.CommandLine; +using System.Reflection; +using System.Text; +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.Data.Sqlite; +using Spectre.Console; + +// Ensure UTF-8 output for Unicode characters +Console.OutputEncoding = Encoding.UTF8; + +var dlensArgument = new Argument("dlens"); +dlensArgument.Description = "Path to the .dlens database file"; + +var outputOption = new Option("--output", "-o"); +outputOption.Description = "Output CSV file path (defaults to collection.csv)"; + +var showTableOption = new Option("--show-table", "-t"); +showTableOption.Description = "Display the card collection as a table"; +showTableOption.DefaultValueFactory = _ => false; + +#if EMBEDDED_APK +var rootCommand = new RootCommand("Extract and display card data from Delver Lens") +{ + dlensArgument, + outputOption, + showTableOption +}; + +rootCommand.SetAction(async (parseResult, cancellationToken) => +{ + var dlensFile = parseResult.GetValue(dlensArgument)!; + var outputFile = parseResult.GetValue(outputOption) ?? new FileInfo("collection.csv"); + var showTable = parseResult.GetValue(showTableOption); + await ProcessFiles(null, dlensFile, outputFile, showTable); +}); +#else +var apkArgument = new Argument("apk"); +apkArgument.Description = "Path to the Delver Lens APK file"; + +var rootCommand = new RootCommand("Extract and display card data from Delver Lens") +{ + apkArgument, + dlensArgument, + outputOption, + showTableOption +}; + +rootCommand.SetAction(async (parseResult, cancellationToken) => +{ + var apkFile = parseResult.GetValue(apkArgument)!; + var dlensFile = parseResult.GetValue(dlensArgument)!; + var outputFile = parseResult.GetValue(outputOption) ?? new FileInfo("collection.csv"); + var showTable = parseResult.GetValue(showTableOption); + await ProcessFiles(apkFile, dlensFile, outputFile, showTable); +}); +#endif + +return await rootCommand.Parse(args).InvokeAsync(); + +async Task ProcessFiles(FileInfo? apkFile, FileInfo dlensFile, FileInfo outputFile, bool showTable) +{ +#if !EMBEDDED_APK + if (apkFile == null || !apkFile.Exists) + { + AnsiConsole.MarkupLine($"[red]APK file not found:[/] {apkFile?.FullName}"); + return; + } +#endif + + if (!dlensFile.Exists) + { + AnsiConsole.MarkupLine($"[red]dlens file not found:[/] {dlensFile.FullName}"); + return; + } + + List? scannedCards = null; + List? collection = null; + var steps = new[] { false, false, false }; + + Panel BuildPanel() + { + var content = $""" + [bold yellow]Progress[/] + + {Step(0, "Read scanned cards from dlens")} + {Step(1, "Resolve card data from APK")} + {Step(2, "Export collection to CSV")} + """; + + if (steps[2]) + { + content += $""" + + + [bold yellow]Summary[/] + + [blue]Your collection:[/] {collection!.Count} unique cards, {collection.Sum(c => c.Quantity)} total + [green]Exported to:[/] {outputFile.FullName} + + [bold yellow]How to import into Archidekt[/] + + 1. Go to [link]https://archidekt.com/collection[/] + 2. Click [yellow]Import[/] + 3. Click [yellow]Add manual column[/] [blue]6 times[/] + 4. Set the columns in order: + • Quantity → [blue]Quantity[/] + • Scryfall ID → [blue]Scryfall ID[/] + • Foil → [blue]Foil[/] + • Card Name → [blue]Ignore[/] + • Set Code → [blue]Ignore[/] + • Collector Number → [blue]Ignore[/] + 5. Set [yellow]Skip first row[/] to [blue]true[/] [grey](the CSV has a header)[/] + 6. Set the csv file by either dragging and dropping it, or clicking the upload box + 7. Click [yellow]Upload[/] + """; + } + + return new Panel(content) + { + Header = new PanelHeader(" Delver Lens → Archidekt "), + Border = BoxBorder.Rounded, + Padding = new Padding(2, 1) + }; + } + + var spinnerFrames = new[] { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }; + var spinnerIndex = 0; + var currentStep = 0; + + string Step(int index, string text) + { + if (steps[index]) + return $"[green][[✓]][/] {text}"; + if (index == currentStep) + return $"[blue][[{spinnerFrames[spinnerIndex]}]][/] {text}"; + return $"[grey][[○]][/] [grey]{text}[/]"; + } + + // When piped, output CSV to stdout for composability + if (Console.IsOutputRedirected) + { + scannedCards = await GetScannedCards(dlensFile); + collection = await ResolveCollection(apkFile, scannedCards); + WriteCsvToStdout(collection); + return; + } + + // Interactive: use live display with progress panel + using var cts = new CancellationTokenSource(); + + await AnsiConsole.Live(BuildPanel()) + .StartAsync(async ctx => + { + // Spinner animation task + var spinnerTask = Task.Run(async () => + { + while (!cts.Token.IsCancellationRequested) + { + await Task.Delay(80, cts.Token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); + spinnerIndex = (spinnerIndex + 1) % spinnerFrames.Length; + ctx.UpdateTarget(BuildPanel()); + } + }); + + scannedCards = await GetScannedCards(dlensFile); + steps[0] = true; + currentStep = 1; + ctx.UpdateTarget(BuildPanel()); + + collection = await ResolveCollection(apkFile, scannedCards); + steps[1] = true; + currentStep = 2; + ctx.UpdateTarget(BuildPanel()); + + await ExportCsv(collection, outputFile); + steps[2] = true; + ctx.UpdateTarget(BuildPanel()); + + cts.Cancel(); + await spinnerTask; + }); + + // Display table if requested (after live panel completes) + if (showTable) + { + DisplayCollection(collection!); + } +} + +async Task> ResolveCollection(FileInfo? apkFile, List scannedCards) +{ + var tempDbPath = Path.GetTempFileName(); + var cardIds = scannedCards.Select(c => c.CardId).ToHashSet(); + + try + { + // Get APK stream from embedded resource or file +#if EMBEDDED_APK + var assembly = Assembly.GetExecutingAssembly(); + await using var apkStream = assembly.GetManifestResourceStream("delver.apk") + ?? throw new Exception("Embedded APK resource not found"); +#else + await using var apkStream = apkFile!.OpenRead(); +#endif + + using (var zipFile = new ZipFile(apkStream)) + { + var entry = zipFile.GetEntry("res/raw/data.db"); + if (entry == null) + { + throw new Exception("Could not find res/raw/data.db in APK"); + } + + await using var zipStream = zipFile.GetInputStream(entry); + await using var outputStream = File.Create(tempDbPath); + await zipStream.CopyToAsync(outputStream); + } + + var cardData = new Dictionary(); + + await using (var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly")) + { + await connection.OpenAsync(); + + await using var cmd = connection.CreateCommand(); + cmd.CommandText = @" + SELECT + c._id, + n.name, + e.tl_abb, + c.number, + c.scryfall_id + FROM cards c + JOIN names n ON c.name = n._id + JOIN editions e ON c.edition = e._id;"; + + await using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var id = reader.GetInt32(0); + if (cardIds.Contains(id)) + { + cardData[id] = ( + reader.GetString(1), + reader.GetString(2), + reader.IsDBNull(3) ? "" : reader.GetString(3), + reader.IsDBNull(4) ? "" : reader.GetString(4) + ); + } + } + } + + var collection = new List(); + foreach (var scanned in scannedCards) + { + if (cardData.TryGetValue(scanned.CardId, out var data)) + { + collection.Add(new CollectionCard( + scanned.Quantity, + data.Name, + data.SetCode, + data.CollectorNumber, + data.ScryfallId, + scanned.Foil + )); + } + else + { + collection.Add(new CollectionCard( + scanned.Quantity, + $"Unknown (ID: {scanned.CardId})", + "", + "", + "", + scanned.Foil + )); + } + } + + return collection; + } + finally + { + SqliteConnection.ClearAllPools(); + if (File.Exists(tempDbPath)) + { + File.Delete(tempDbPath); + } + } +} + +void DisplayCollection(List collection) +{ + var table = new Table(); + table.Border = TableBorder.Rounded; + table.AddColumn("Qty"); + table.AddColumn("Name"); + table.AddColumn("Set"); + table.AddColumn("#"); + table.AddColumn("Foil"); + table.AddColumn("Scryfall ID"); + + foreach (var card in collection.OrderBy(c => c.Name).ThenBy(c => c.SetCode)) + { + table.AddRow( + card.Quantity.ToString(), + card.Name.Length > 30 ? card.Name[..27] + "..." : card.Name, + card.SetCode, + card.CollectorNumber, + card.Foil ? "[yellow]Yes[/]" : "", + card.ScryfallId.Length > 8 ? card.ScryfallId[..8] + "..." : card.ScryfallId + ); + } + + AnsiConsole.Write(table); +} + +async Task ExportCsv(List collection, FileInfo outputFile) +{ + var sb = new StringBuilder(); + sb.AppendLine("Quantity,Scryfall ID,Foil,Card Name,Set Code,Collector Number"); + + foreach (var card in collection.OrderBy(c => c.Name).ThenBy(c => c.SetCode)) + { + var foilStr = card.Foil ? "Foil" : "Normal"; + var name = card.Name.Contains(',') ? $"\"{card.Name}\"" : card.Name; + sb.AppendLine($"{card.Quantity},{card.ScryfallId},{foilStr},{name},{card.SetCode},{card.CollectorNumber}"); + } + + await File.WriteAllTextAsync(outputFile.FullName, sb.ToString()); +} + +void WriteCsvToStdout(List collection) +{ + Console.WriteLine("Quantity,Scryfall ID,Foil,Card Name,Set Code,Collector Number"); + + foreach (var card in collection.OrderBy(c => c.Name).ThenBy(c => c.SetCode)) + { + var foilStr = card.Foil ? "Foil" : "Normal"; + var name = card.Name.Contains(',') ? $"\"{card.Name}\"" : card.Name; + Console.WriteLine($"{card.Quantity},{card.ScryfallId},{foilStr},{name},{card.SetCode},{card.CollectorNumber}"); + } +} + +async Task> GetScannedCards(FileInfo dlensFile) +{ + var cards = new List(); + + await using var connection = new SqliteConnection($"Data Source={dlensFile.FullName};Mode=ReadOnly"); + await connection.OpenAsync(); + + await using var command = connection.CreateCommand(); + command.CommandText = "SELECT * FROM cards"; + + await using var reader = await command.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var cardId = reader.GetInt32(reader.GetOrdinal("card")); + var quantity = reader.GetInt32(reader.GetOrdinal("quantity")); + var foil = reader.GetInt32(reader.GetOrdinal("foil")) == 1; + + cards.Add(new ScannedCard(cardId, quantity, foil)); + } + + return cards; +} + +record ScannedCard(int CardId, int Quantity, bool Foil); +record CollectionCard(int Quantity, string Name, string SetCode, string CollectorNumber, string ScryfallId, bool Foil); diff --git a/Scry.csproj b/Scry.csproj new file mode 100644 index 0000000..b3f1ef6 --- /dev/null +++ b/Scry.csproj @@ -0,0 +1,33 @@ + + + + Exe + net10.0 + enable + enable + true + true + true + true + none + false + + + + $(DefineConstants);EMBEDDED_APK + + + + + delver.apk + + + + + + + + + + + diff --git a/TestImages/README.md b/TestImages/README.md deleted file mode 100644 index 799ba29..0000000 --- a/TestImages/README.md +++ /dev/null @@ -1,151 +0,0 @@ -# Test Images - -This directory contains **225 reference images** for testing card recognition algorithms without requiring actual hardware. - -## Directory Structure - -| Category | Count | Description | -|----------|-------|-------------| -| `reference_alpha/` | 47 | Alpha edition reference cards (old frame) | -| `varying_quality/` | 38 | Different lighting, blur, exposure, angles | -| `single_cards/` | 19 | Individual card photos | -| `real_photos/` | 18 | Phone camera photos from Visions project | -| `foreign/` | 16 | Non-English cards (Japanese, German, French, etc.) | -| `worn/` | 15 | Heavily played, damaged, worn cards | -| `foil/` | 14 | Foil cards with holographic glare/reflections | -| `low_light/` | 14 | Poor lighting, glare, shadows, amateur photos | -| `tokens/` | 13 | Tokens and planeswalker emblems | -| `hands/` | 11 | Cards held in hand (partial visibility) | -| `ocr_test/` | 10 | Images optimized for OCR testing | -| `reference/` | 9 | High-quality reference scans | -| `multiple_cards/` | 6 | Multiple cards in frame | -| `augmented/` | 4 | Augmented training examples | -| `training_examples/` | 3 | ML training set samples | -| `angled/` | 2 | Perspective distortion | - -## Image Sources - -Images from open-source MIT-licensed projects: - -- [hj3yoo/mtg_card_detector](https://github.com/hj3yoo/mtg_card_detector) -- [tmikonen/magic_card_detector](https://github.com/tmikonen/magic_card_detector) -- [fortierq/mtgscan](https://github.com/fortierq/mtgscan) -- [LauriHursti/visions](https://github.com/LauriHursti/visions) -- [KLuml/CardScanner](https://github.com/KLuml/CardScanner) -- [dills122/MTG-Card-Analyzer](https://github.com/dills122/MTG-Card-Analyzer) -- [ryanlin/Turtle](https://github.com/ryanlin/Turtle) - -Additional images from: -- Reddit r/magicTCG (user-submitted photos) -- Flickr (Creative Commons) -- Card Kingdom / Face to Face Games grading guides -- Scryfall (foreign language card scans) - -## Usage - -```csharp -[Theory] -[InlineData("varying_quality/test1.jpg")] -[InlineData("angled/tilted_card_1.jpg")] -[InlineData("hands/hand_of_card_1.png")] -[InlineData("foil/rainbow_foil_secret_lair.jpg")] -[InlineData("worn/hp_shuffle_crease.webp")] -[InlineData("foreign/japanese_aang.jpg")] -public async Task RecognizeCard_VaryingConditions(string imagePath) -{ - using var stream = File.OpenRead(Path.Combine("TestImages", imagePath)); - var result = await _recognitionService.RecognizeCardAsync(stream); - - Assert.True(result.Success); - Assert.NotNull(result.Card); - Assert.True(result.Confidence >= 0.7f); -} -``` - -## Category Details - -### foil/ -Foil cards showing holographic effects that challenge recognition: -- Rainbow foils with color-shifting (`rainbow_foil_secret_lair.jpg`) -- Surge foils with holo stickers (`surge_foils_holo.jpeg`) -- Old-style foils (`old_foil_yawgmoth.jpg`) -- Textured/dragonscale foils (`dragonscale_foil.jpg`) -- Foil curling examples showing warping - -### worn/ -Heavily played and damaged cards: -- Edge whitening (`edge_white.png`, `very_good_*.jpg`) -- Scratches and scuffs (`scratch.png`, `hp_scratches.png`) -- Creases and bends (`hp_shuffle_crease.webp`, `bent_creased.jpg`) -- Binder damage (`hp_binder_bite_*.webp`) -- Water damage (`hp_water_warping.png`) -- Corner damage (`hp_compromised_corner.webp`) - -### low_light/ -Poor lighting and amateur photography conditions: -- Glare from toploaders/sleeves (`glare_toploader.png`) -- Direct light causing hotspots (`glare_straight_down.jpg`) -- Depth of field blur (`dof_blur_amateur.jpg`) -- Amateur condition photos with shadows -- Flickr collection shots with mixed lighting - -### foreign/ -Non-English cards (8 languages): -- Japanese (日本語) -- German (Deutsch) -- French (Français) -- Italian (Italiano) -- Spanish (Español) -- Russian (Русский) -- Simplified Chinese (简体中文) -- Korean (한국어) - -### tokens/ -Tokens and planeswalker emblems: -- Official WotC tokens -- Custom/altered tokens -- Planeswalker emblems (Elspeth, Gideon, Narset) -- Token collections and gameplay shots - -### varying_quality/ -Images with various real-world challenges: -- Different camera exposures -- BGS graded cases (`counterspell_bgs.jpg`) -- Cards in plastic sleeves (`card_in_plastic_case.jpg`) -- Various lighting conditions -- 28 numbered test images (`test1.jpg` - `test27.jpg`) - -### reference_alpha/ -47 Limited Edition Alpha cards for old-frame recognition: -- Power Nine (Black Lotus, Ancestral Recall, Moxen, etc.) -- Dual lands (Underground Sea, Volcanic Island, etc.) -- Classic staples (Lightning Bolt, Counterspell, Sol Ring) - -### hands/ -Cards held in hand - partial visibility, stacked: -- Various deck archetypes (Tron, Green, Red) -- New and old frame cards -- Different lighting conditions - -### real_photos/ -Phone camera photos from Visions project: -- Real-world scanning conditions -- Various resolutions and crops -- Includes processed result images - -### ocr_test/ -From CardScanner project, graded by difficulty: -- `card0-4.jpg`: Easier recognition -- `card10-13.jpg`: Harder recognition (noted ~less accuracy) - -## TODO: Additional Categories Needed - -- [ ] **double_faced/** - Transform/MDFC cards (both sides) -- [ ] **art_cards/** - Art series cards without text boxes -- [ ] **promos/** - Extended art, borderless, showcase frames -- [ ] **very_low_light/** - Near-dark conditions -- [ ] **motion_blur/** - Cards in motion during scanning - -## License - -Card artwork is property of Wizards of the Coast. Images used for testing/research purposes only. diff --git a/TestImages/angled/tilted_card_1.jpg b/TestImages/angled/tilted_card_1.jpg deleted file mode 100644 index e973651..0000000 Binary files a/TestImages/angled/tilted_card_1.jpg and /dev/null differ diff --git a/TestImages/angled/tilted_card_2.jpg b/TestImages/angled/tilted_card_2.jpg deleted file mode 100644 index d1edf41..0000000 Binary files a/TestImages/angled/tilted_card_2.jpg and /dev/null differ diff --git a/TestImages/augmented/augmented_1.jpg b/TestImages/augmented/augmented_1.jpg deleted file mode 100644 index 1f75ac5..0000000 Binary files a/TestImages/augmented/augmented_1.jpg and /dev/null differ diff --git a/TestImages/augmented/augmented_2.jpg b/TestImages/augmented/augmented_2.jpg deleted file mode 100644 index 5eb1c4e..0000000 Binary files a/TestImages/augmented/augmented_2.jpg and /dev/null differ diff --git a/TestImages/augmented/augmented_3.jpg b/TestImages/augmented/augmented_3.jpg deleted file mode 100644 index 9d41972..0000000 Binary files a/TestImages/augmented/augmented_3.jpg and /dev/null differ diff --git a/TestImages/augmented/augmented_4.jpg b/TestImages/augmented/augmented_4.jpg deleted file mode 100644 index d4897ae..0000000 Binary files a/TestImages/augmented/augmented_4.jpg and /dev/null differ diff --git a/TestImages/foil/dragonscale_foil.jpg b/TestImages/foil/dragonscale_foil.jpg deleted file mode 100644 index ce1bbfc..0000000 Binary files a/TestImages/foil/dragonscale_foil.jpg and /dev/null differ diff --git a/TestImages/foil/foil_curling_1.jpg b/TestImages/foil/foil_curling_1.jpg deleted file mode 100644 index 6d20039..0000000 Binary files a/TestImages/foil/foil_curling_1.jpg and /dev/null differ diff --git a/TestImages/foil/foil_curling_2.jpg b/TestImages/foil/foil_curling_2.jpg deleted file mode 100644 index a37dfcd..0000000 Binary files a/TestImages/foil/foil_curling_2.jpg and /dev/null differ diff --git a/TestImages/foil/foil_jpn_mystical_archives.jpg b/TestImages/foil/foil_jpn_mystical_archives.jpg deleted file mode 100644 index 436d844..0000000 Binary files a/TestImages/foil/foil_jpn_mystical_archives.jpg and /dev/null differ diff --git a/TestImages/foil/foil_peel_holo_layer.jpg b/TestImages/foil/foil_peel_holo_layer.jpg deleted file mode 100644 index f19ee43..0000000 Binary files a/TestImages/foil/foil_peel_holo_layer.jpg and /dev/null differ diff --git a/TestImages/foil/foil_quality_comparison.jpeg b/TestImages/foil/foil_quality_comparison.jpeg deleted file mode 100644 index 7899964..0000000 Binary files a/TestImages/foil/foil_quality_comparison.jpeg and /dev/null differ diff --git a/TestImages/foil/foil_swamp_collection.jpg b/TestImages/foil/foil_swamp_collection.jpg deleted file mode 100644 index 3111393..0000000 Binary files a/TestImages/foil/foil_swamp_collection.jpg and /dev/null differ diff --git a/TestImages/foil/modern_vs_og_foils.jpg b/TestImages/foil/modern_vs_og_foils.jpg deleted file mode 100644 index 7a04366..0000000 Binary files a/TestImages/foil/modern_vs_og_foils.jpg and /dev/null differ diff --git a/TestImages/foil/old_foil_yawgmoth.jpg b/TestImages/foil/old_foil_yawgmoth.jpg deleted file mode 100644 index 5c72d87..0000000 Binary files a/TestImages/foil/old_foil_yawgmoth.jpg and /dev/null differ diff --git a/TestImages/foil/rainbow_foil_secret_lair.jpg b/TestImages/foil/rainbow_foil_secret_lair.jpg deleted file mode 100644 index 11aa32a..0000000 Binary files a/TestImages/foil/rainbow_foil_secret_lair.jpg and /dev/null differ diff --git a/TestImages/foil/rainbow_foil_sheldons.jpg b/TestImages/foil/rainbow_foil_sheldons.jpg deleted file mode 100644 index e4e3072..0000000 Binary files a/TestImages/foil/rainbow_foil_sheldons.jpg and /dev/null differ diff --git a/TestImages/foil/surge_foil_rhino.jpeg b/TestImages/foil/surge_foil_rhino.jpeg deleted file mode 100644 index c9c48ea..0000000 Binary files a/TestImages/foil/surge_foil_rhino.jpeg and /dev/null differ diff --git a/TestImages/foil/surge_foils_holo.jpeg b/TestImages/foil/surge_foils_holo.jpeg deleted file mode 100644 index fd3e806..0000000 Binary files a/TestImages/foil/surge_foils_holo.jpeg and /dev/null differ diff --git a/TestImages/foil/textured_foils.jpg b/TestImages/foil/textured_foils.jpg deleted file mode 100644 index 5e204dd..0000000 Binary files a/TestImages/foil/textured_foils.jpg and /dev/null differ diff --git a/TestImages/foreign/chinese_aarakocra.jpg b/TestImages/foreign/chinese_aarakocra.jpg deleted file mode 100644 index 6d092a3..0000000 Binary files a/TestImages/foreign/chinese_aarakocra.jpg and /dev/null differ diff --git a/TestImages/foreign/chinese_abattoir_ghoul.jpg b/TestImages/foreign/chinese_abattoir_ghoul.jpg deleted file mode 100644 index 1abb3e0..0000000 Binary files a/TestImages/foreign/chinese_abattoir_ghoul.jpg and /dev/null differ diff --git a/TestImages/foreign/french_aang.jpg b/TestImages/foreign/french_aang.jpg deleted file mode 100644 index e4955a1..0000000 Binary files a/TestImages/foreign/french_aang.jpg and /dev/null differ diff --git a/TestImages/foreign/french_abattoir_ghoul.jpg b/TestImages/foreign/french_abattoir_ghoul.jpg deleted file mode 100644 index 16090fc..0000000 Binary files a/TestImages/foreign/french_abattoir_ghoul.jpg and /dev/null differ diff --git a/TestImages/foreign/german_aang.jpg b/TestImages/foreign/german_aang.jpg deleted file mode 100644 index d0615f9..0000000 Binary files a/TestImages/foreign/german_aang.jpg and /dev/null differ diff --git a/TestImages/foreign/german_abattoir_ghoul.jpg b/TestImages/foreign/german_abattoir_ghoul.jpg deleted file mode 100644 index 382f59c..0000000 Binary files a/TestImages/foreign/german_abattoir_ghoul.jpg and /dev/null differ diff --git a/TestImages/foreign/italian_aang.jpg b/TestImages/foreign/italian_aang.jpg deleted file mode 100644 index c18f85a..0000000 Binary files a/TestImages/foreign/italian_aang.jpg and /dev/null differ diff --git a/TestImages/foreign/japanese_aang.jpg b/TestImages/foreign/japanese_aang.jpg deleted file mode 100644 index 2a3fec1..0000000 Binary files a/TestImages/foreign/japanese_aang.jpg and /dev/null differ diff --git a/TestImages/foreign/japanese_abduction.jpg b/TestImages/foreign/japanese_abduction.jpg deleted file mode 100644 index 0f7dc2d..0000000 Binary files a/TestImages/foreign/japanese_abduction.jpg and /dev/null differ diff --git a/TestImages/foreign/japanese_aberrant_researcher.jpg b/TestImages/foreign/japanese_aberrant_researcher.jpg deleted file mode 100644 index 9906fd1..0000000 Binary files a/TestImages/foreign/japanese_aberrant_researcher.jpg and /dev/null differ diff --git a/TestImages/foreign/japanese_abhorrent_overlord.jpg b/TestImages/foreign/japanese_abhorrent_overlord.jpg deleted file mode 100644 index f81b500..0000000 Binary files a/TestImages/foreign/japanese_abhorrent_overlord.jpg and /dev/null differ diff --git a/TestImages/foreign/korean_aarakocra.jpg b/TestImages/foreign/korean_aarakocra.jpg deleted file mode 100644 index 0239a26..0000000 Binary files a/TestImages/foreign/korean_aarakocra.jpg and /dev/null differ diff --git a/TestImages/foreign/korean_abattoir_ghoul.jpg b/TestImages/foreign/korean_abattoir_ghoul.jpg deleted file mode 100644 index d764f62..0000000 Binary files a/TestImages/foreign/korean_abattoir_ghoul.jpg and /dev/null differ diff --git a/TestImages/foreign/russian_aarakocra.jpg b/TestImages/foreign/russian_aarakocra.jpg deleted file mode 100644 index 6470a93..0000000 Binary files a/TestImages/foreign/russian_aarakocra.jpg and /dev/null differ diff --git a/TestImages/foreign/russian_abattoir_ghoul.jpg b/TestImages/foreign/russian_abattoir_ghoul.jpg deleted file mode 100644 index b43afff..0000000 Binary files a/TestImages/foreign/russian_abattoir_ghoul.jpg and /dev/null differ diff --git a/TestImages/foreign/spanish_aang.jpg b/TestImages/foreign/spanish_aang.jpg deleted file mode 100644 index b222c29..0000000 Binary files a/TestImages/foreign/spanish_aang.jpg and /dev/null differ diff --git a/TestImages/hands/handOfCards.jpg b/TestImages/hands/handOfCards.jpg deleted file mode 100644 index 8f8f53e..0000000 Binary files a/TestImages/hands/handOfCards.jpg and /dev/null differ diff --git a/TestImages/hands/hand_of_card_1.png b/TestImages/hands/hand_of_card_1.png deleted file mode 100644 index 8323d5c..0000000 Binary files a/TestImages/hands/hand_of_card_1.png and /dev/null differ diff --git a/TestImages/hands/hand_of_card_green_1.jpg b/TestImages/hands/hand_of_card_green_1.jpg deleted file mode 100644 index 13f5b75..0000000 Binary files a/TestImages/hands/hand_of_card_green_1.jpg and /dev/null differ diff --git a/TestImages/hands/hand_of_card_green_2.jpeg b/TestImages/hands/hand_of_card_green_2.jpeg deleted file mode 100644 index 86109fa..0000000 Binary files a/TestImages/hands/hand_of_card_green_2.jpeg and /dev/null differ diff --git a/TestImages/hands/hand_of_card_ktk.png b/TestImages/hands/hand_of_card_ktk.png deleted file mode 100644 index 456ab69..0000000 Binary files a/TestImages/hands/hand_of_card_ktk.png and /dev/null differ diff --git a/TestImages/hands/hand_of_card_new_frame.webp b/TestImages/hands/hand_of_card_new_frame.webp deleted file mode 100644 index 1eb5b04..0000000 Binary files a/TestImages/hands/hand_of_card_new_frame.webp and /dev/null differ diff --git a/TestImages/hands/hand_of_card_one_hand.jpg b/TestImages/hands/hand_of_card_one_hand.jpg deleted file mode 100644 index bae5d8d..0000000 Binary files a/TestImages/hands/hand_of_card_one_hand.jpg and /dev/null differ diff --git a/TestImages/hands/hand_of_card_red.jpeg b/TestImages/hands/hand_of_card_red.jpeg deleted file mode 100644 index 4469e9f..0000000 Binary files a/TestImages/hands/hand_of_card_red.jpeg and /dev/null differ diff --git a/TestImages/hands/hand_of_card_tron.png b/TestImages/hands/hand_of_card_tron.png deleted file mode 100644 index b2f569c..0000000 Binary files a/TestImages/hands/hand_of_card_tron.png and /dev/null differ diff --git a/TestImages/hands/klomparens_hand.png b/TestImages/hands/klomparens_hand.png deleted file mode 100644 index 09cc0b3..0000000 Binary files a/TestImages/hands/klomparens_hand.png and /dev/null differ diff --git a/TestImages/hands/li38_handOfCards.jpg b/TestImages/hands/li38_handOfCards.jpg deleted file mode 100644 index e7e91be..0000000 Binary files a/TestImages/hands/li38_handOfCards.jpg and /dev/null differ diff --git a/TestImages/low_light/authenticity_check.jpg b/TestImages/low_light/authenticity_check.jpg deleted file mode 100644 index 7618852..0000000 Binary files a/TestImages/low_light/authenticity_check.jpg and /dev/null differ diff --git a/TestImages/low_light/basic_lands_amateur.jpg b/TestImages/low_light/basic_lands_amateur.jpg deleted file mode 100644 index f95979b..0000000 Binary files a/TestImages/low_light/basic_lands_amateur.jpg and /dev/null differ diff --git a/TestImages/low_light/condition_amateur_1.jpg b/TestImages/low_light/condition_amateur_1.jpg deleted file mode 100644 index 46b0d27..0000000 Binary files a/TestImages/low_light/condition_amateur_1.jpg and /dev/null differ diff --git a/TestImages/low_light/condition_amateur_2.jpg b/TestImages/low_light/condition_amateur_2.jpg deleted file mode 100644 index 49d0e2b..0000000 Binary files a/TestImages/low_light/condition_amateur_2.jpg and /dev/null differ diff --git a/TestImages/low_light/diy_lighting_rig.jpg b/TestImages/low_light/diy_lighting_rig.jpg deleted file mode 100644 index e49fb06..0000000 Binary files a/TestImages/low_light/diy_lighting_rig.jpg and /dev/null differ diff --git a/TestImages/low_light/dof_blur_amateur.jpg b/TestImages/low_light/dof_blur_amateur.jpg deleted file mode 100644 index 9e3a974..0000000 Binary files a/TestImages/low_light/dof_blur_amateur.jpg and /dev/null differ diff --git a/TestImages/low_light/fake_detection.jpg b/TestImages/low_light/fake_detection.jpg deleted file mode 100644 index 54f1bdd..0000000 Binary files a/TestImages/low_light/fake_detection.jpg and /dev/null differ diff --git a/TestImages/low_light/flickr_collection_1.jpg b/TestImages/low_light/flickr_collection_1.jpg deleted file mode 100644 index 057b426..0000000 Binary files a/TestImages/low_light/flickr_collection_1.jpg and /dev/null differ diff --git a/TestImages/low_light/flickr_collection_2.jpg b/TestImages/low_light/flickr_collection_2.jpg deleted file mode 100644 index 6764c6e..0000000 Binary files a/TestImages/low_light/flickr_collection_2.jpg and /dev/null differ diff --git a/TestImages/low_light/flickr_collection_3.jpg b/TestImages/low_light/flickr_collection_3.jpg deleted file mode 100644 index f7e6483..0000000 Binary files a/TestImages/low_light/flickr_collection_3.jpg and /dev/null differ diff --git a/TestImages/low_light/glare_straight_down.jpg b/TestImages/low_light/glare_straight_down.jpg deleted file mode 100644 index fdf5838..0000000 Binary files a/TestImages/low_light/glare_straight_down.jpg and /dev/null differ diff --git a/TestImages/low_light/glare_toploader.png b/TestImages/low_light/glare_toploader.png deleted file mode 100644 index 5a3f6b2..0000000 Binary files a/TestImages/low_light/glare_toploader.png and /dev/null differ diff --git a/TestImages/low_light/grading_amateur.jpg b/TestImages/low_light/grading_amateur.jpg deleted file mode 100644 index 8a7a040..0000000 Binary files a/TestImages/low_light/grading_amateur.jpg and /dev/null differ diff --git a/TestImages/low_light/macro_monday_shadows.jpg b/TestImages/low_light/macro_monday_shadows.jpg deleted file mode 100644 index bf47519..0000000 Binary files a/TestImages/low_light/macro_monday_shadows.jpg and /dev/null differ diff --git a/TestImages/multiple_cards/alpha_deck.jpg b/TestImages/multiple_cards/alpha_deck.jpg deleted file mode 100644 index 281ff42..0000000 Binary files a/TestImages/multiple_cards/alpha_deck.jpg and /dev/null differ diff --git a/TestImages/multiple_cards/geyser_twister_fireball.jpg b/TestImages/multiple_cards/geyser_twister_fireball.jpg deleted file mode 100644 index 47263a3..0000000 Binary files a/TestImages/multiple_cards/geyser_twister_fireball.jpg and /dev/null differ diff --git a/TestImages/multiple_cards/lands_and_fatties.jpg b/TestImages/multiple_cards/lands_and_fatties.jpg deleted file mode 100644 index 344b26b..0000000 Binary files a/TestImages/multiple_cards/lands_and_fatties.jpg and /dev/null differ diff --git a/TestImages/multiple_cards/magic1.png b/TestImages/multiple_cards/magic1.png deleted file mode 100644 index a6480fb..0000000 Binary files a/TestImages/multiple_cards/magic1.png and /dev/null differ diff --git a/TestImages/multiple_cards/pro_tour_side.png b/TestImages/multiple_cards/pro_tour_side.png deleted file mode 100644 index 759ddf3..0000000 Binary files a/TestImages/multiple_cards/pro_tour_side.png and /dev/null differ diff --git a/TestImages/multiple_cards/pro_tour_table.png b/TestImages/multiple_cards/pro_tour_table.png deleted file mode 100644 index e02960b..0000000 Binary files a/TestImages/multiple_cards/pro_tour_table.png and /dev/null differ diff --git a/TestImages/ocr_test/card.jpg b/TestImages/ocr_test/card.jpg deleted file mode 100644 index ff57b28..0000000 Binary files a/TestImages/ocr_test/card.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card0.jpg b/TestImages/ocr_test/card0.jpg deleted file mode 100644 index 5a5f5d8..0000000 Binary files a/TestImages/ocr_test/card0.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card1.jpg b/TestImages/ocr_test/card1.jpg deleted file mode 100644 index 151d89f..0000000 Binary files a/TestImages/ocr_test/card1.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card10.jpg b/TestImages/ocr_test/card10.jpg deleted file mode 100644 index 1d25cf2..0000000 Binary files a/TestImages/ocr_test/card10.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card11.jpg b/TestImages/ocr_test/card11.jpg deleted file mode 100644 index 339fc0c..0000000 Binary files a/TestImages/ocr_test/card11.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card12.jpg b/TestImages/ocr_test/card12.jpg deleted file mode 100644 index 4de7f50..0000000 Binary files a/TestImages/ocr_test/card12.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card13.jpg b/TestImages/ocr_test/card13.jpg deleted file mode 100644 index 3b96f8d..0000000 Binary files a/TestImages/ocr_test/card13.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card2.jpg b/TestImages/ocr_test/card2.jpg deleted file mode 100644 index b974812..0000000 Binary files a/TestImages/ocr_test/card2.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card3.jpg b/TestImages/ocr_test/card3.jpg deleted file mode 100644 index 56347eb..0000000 Binary files a/TestImages/ocr_test/card3.jpg and /dev/null differ diff --git a/TestImages/ocr_test/card4.jpg b/TestImages/ocr_test/card4.jpg deleted file mode 100644 index 4e73d9c..0000000 Binary files a/TestImages/ocr_test/card4.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_1.jpg b/TestImages/real_photos/visions_1.jpg deleted file mode 100644 index 9408b6d..0000000 Binary files a/TestImages/real_photos/visions_1.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_1_square.jpg b/TestImages/real_photos/visions_1_square.jpg deleted file mode 100644 index a15da3e..0000000 Binary files a/TestImages/real_photos/visions_1_square.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_2.jpg b/TestImages/real_photos/visions_2.jpg deleted file mode 100644 index 04878b2..0000000 Binary files a/TestImages/real_photos/visions_2.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_2_square.jpg b/TestImages/real_photos/visions_2_square.jpg deleted file mode 100644 index 389a603..0000000 Binary files a/TestImages/real_photos/visions_2_square.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_3.jpg b/TestImages/real_photos/visions_3.jpg deleted file mode 100644 index 5fcc36b..0000000 Binary files a/TestImages/real_photos/visions_3.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_4.jpg b/TestImages/real_photos/visions_4.jpg deleted file mode 100644 index 2664cca..0000000 Binary files a/TestImages/real_photos/visions_4.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_5.jpg b/TestImages/real_photos/visions_5.jpg deleted file mode 100644 index 67ef2f0..0000000 Binary files a/TestImages/real_photos/visions_5.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_6.jpg b/TestImages/real_photos/visions_6.jpg deleted file mode 100644 index 39b27fd..0000000 Binary files a/TestImages/real_photos/visions_6.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_6_square.jpg b/TestImages/real_photos/visions_6_square.jpg deleted file mode 100644 index 15bd9bc..0000000 Binary files a/TestImages/real_photos/visions_6_square.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_7.jpg b/TestImages/real_photos/visions_7.jpg deleted file mode 100644 index 4a5525b..0000000 Binary files a/TestImages/real_photos/visions_7.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_8.jpg b/TestImages/real_photos/visions_8.jpg deleted file mode 100644 index 5205411..0000000 Binary files a/TestImages/real_photos/visions_8.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_8_big.jpg b/TestImages/real_photos/visions_8_big.jpg deleted file mode 100644 index aacdb0a..0000000 Binary files a/TestImages/real_photos/visions_8_big.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_9.jpg b/TestImages/real_photos/visions_9.jpg deleted file mode 100644 index 04cb000..0000000 Binary files a/TestImages/real_photos/visions_9.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_9_small.jpg b/TestImages/real_photos/visions_9_small.jpg deleted file mode 100644 index 230f5b2..0000000 Binary files a/TestImages/real_photos/visions_9_small.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_result_1.jpg b/TestImages/real_photos/visions_result_1.jpg deleted file mode 100644 index a669ee2..0000000 Binary files a/TestImages/real_photos/visions_result_1.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_result_2.jpg b/TestImages/real_photos/visions_result_2.jpg deleted file mode 100644 index abd29ed..0000000 Binary files a/TestImages/real_photos/visions_result_2.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_result_3.jpg b/TestImages/real_photos/visions_result_3.jpg deleted file mode 100644 index 988e068..0000000 Binary files a/TestImages/real_photos/visions_result_3.jpg and /dev/null differ diff --git a/TestImages/real_photos/visions_result_4.jpg b/TestImages/real_photos/visions_result_4.jpg deleted file mode 100644 index a28fd0a..0000000 Binary files a/TestImages/real_photos/visions_result_4.jpg and /dev/null differ diff --git a/TestImages/reference/brainstorm.png b/TestImages/reference/brainstorm.png deleted file mode 100644 index bf7f8f5..0000000 Binary files a/TestImages/reference/brainstorm.png and /dev/null differ diff --git a/TestImages/reference/force_of_will.png b/TestImages/reference/force_of_will.png deleted file mode 100644 index 6ec00e5..0000000 Binary files a/TestImages/reference/force_of_will.png and /dev/null differ diff --git a/TestImages/reference/griselbrand.png b/TestImages/reference/griselbrand.png deleted file mode 100644 index e73c642..0000000 Binary files a/TestImages/reference/griselbrand.png and /dev/null differ diff --git a/TestImages/reference/lotus_petal.png b/TestImages/reference/lotus_petal.png deleted file mode 100644 index d048c9f..0000000 Binary files a/TestImages/reference/lotus_petal.png and /dev/null differ diff --git a/TestImages/reference/ponder.png b/TestImages/reference/ponder.png deleted file mode 100644 index 48ae59d..0000000 Binary files a/TestImages/reference/ponder.png and /dev/null differ diff --git a/TestImages/reference/show_and_tell.png b/TestImages/reference/show_and_tell.png deleted file mode 100644 index 9dee849..0000000 Binary files a/TestImages/reference/show_and_tell.png and /dev/null differ diff --git a/TestImages/reference/tropical_island.png b/TestImages/reference/tropical_island.png deleted file mode 100644 index 5ddb71f..0000000 Binary files a/TestImages/reference/tropical_island.png and /dev/null differ diff --git a/TestImages/reference/volcanic_island.png b/TestImages/reference/volcanic_island.png deleted file mode 100644 index d14eb98..0000000 Binary files a/TestImages/reference/volcanic_island.png and /dev/null differ diff --git a/TestImages/reference/wasteland.png b/TestImages/reference/wasteland.png deleted file mode 100644 index 54b12ab..0000000 Binary files a/TestImages/reference/wasteland.png and /dev/null differ diff --git a/TestImages/reference_alpha/ancestral_recall.jpg b/TestImages/reference_alpha/ancestral_recall.jpg deleted file mode 100644 index 273d451..0000000 Binary files a/TestImages/reference_alpha/ancestral_recall.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/badlands.jpg b/TestImages/reference_alpha/badlands.jpg deleted file mode 100644 index 34b8f20..0000000 Binary files a/TestImages/reference_alpha/badlands.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/balance.jpg b/TestImages/reference_alpha/balance.jpg deleted file mode 100644 index 004e76b..0000000 Binary files a/TestImages/reference_alpha/balance.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/bayou.jpg b/TestImages/reference_alpha/bayou.jpg deleted file mode 100644 index 77ed6ab..0000000 Binary files a/TestImages/reference_alpha/bayou.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/birds_of_paradise.jpg b/TestImages/reference_alpha/birds_of_paradise.jpg deleted file mode 100644 index 83407e2..0000000 Binary files a/TestImages/reference_alpha/birds_of_paradise.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/black_lotus.jpg b/TestImages/reference_alpha/black_lotus.jpg deleted file mode 100644 index b529a2b..0000000 Binary files a/TestImages/reference_alpha/black_lotus.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/channel.jpg b/TestImages/reference_alpha/channel.jpg deleted file mode 100644 index ea61345..0000000 Binary files a/TestImages/reference_alpha/channel.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/chaos_orb.jpg b/TestImages/reference_alpha/chaos_orb.jpg deleted file mode 100644 index d67b23a..0000000 Binary files a/TestImages/reference_alpha/chaos_orb.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/clone.jpg b/TestImages/reference_alpha/clone.jpg deleted file mode 100644 index 937461a..0000000 Binary files a/TestImages/reference_alpha/clone.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/control_magic.jpg b/TestImages/reference_alpha/control_magic.jpg deleted file mode 100644 index 51f94d9..0000000 Binary files a/TestImages/reference_alpha/control_magic.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/counterspell.jpg b/TestImages/reference_alpha/counterspell.jpg deleted file mode 100644 index 44a134c..0000000 Binary files a/TestImages/reference_alpha/counterspell.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/dark_ritual.jpg b/TestImages/reference_alpha/dark_ritual.jpg deleted file mode 100644 index 92829be..0000000 Binary files a/TestImages/reference_alpha/dark_ritual.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/demonic_tutor.jpg b/TestImages/reference_alpha/demonic_tutor.jpg deleted file mode 100644 index bf0375d..0000000 Binary files a/TestImages/reference_alpha/demonic_tutor.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/disenchant.jpg b/TestImages/reference_alpha/disenchant.jpg deleted file mode 100644 index a159c61..0000000 Binary files a/TestImages/reference_alpha/disenchant.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/fireball.jpg b/TestImages/reference_alpha/fireball.jpg deleted file mode 100644 index a683353..0000000 Binary files a/TestImages/reference_alpha/fireball.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/force_of_nature.jpg b/TestImages/reference_alpha/force_of_nature.jpg deleted file mode 100644 index 497c7c5..0000000 Binary files a/TestImages/reference_alpha/force_of_nature.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/fork.jpg b/TestImages/reference_alpha/fork.jpg deleted file mode 100644 index 40ac20d..0000000 Binary files a/TestImages/reference_alpha/fork.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/giant_growth.jpg b/TestImages/reference_alpha/giant_growth.jpg deleted file mode 100644 index 45bc473..0000000 Binary files a/TestImages/reference_alpha/giant_growth.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/hypnotic_specter.jpg b/TestImages/reference_alpha/hypnotic_specter.jpg deleted file mode 100644 index 11ebb95..0000000 Binary files a/TestImages/reference_alpha/hypnotic_specter.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/lightning_bolt.jpg b/TestImages/reference_alpha/lightning_bolt.jpg deleted file mode 100644 index 710b69a..0000000 Binary files a/TestImages/reference_alpha/lightning_bolt.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/llanowar_elves.jpg b/TestImages/reference_alpha/llanowar_elves.jpg deleted file mode 100644 index bdfbfc1..0000000 Binary files a/TestImages/reference_alpha/llanowar_elves.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mahamoti_djinn.jpg b/TestImages/reference_alpha/mahamoti_djinn.jpg deleted file mode 100644 index 5265950..0000000 Binary files a/TestImages/reference_alpha/mahamoti_djinn.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mind_twist.jpg b/TestImages/reference_alpha/mind_twist.jpg deleted file mode 100644 index 6ee690b..0000000 Binary files a/TestImages/reference_alpha/mind_twist.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mox_emerald.jpg b/TestImages/reference_alpha/mox_emerald.jpg deleted file mode 100644 index 25c0e11..0000000 Binary files a/TestImages/reference_alpha/mox_emerald.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mox_jet.jpg b/TestImages/reference_alpha/mox_jet.jpg deleted file mode 100644 index a3e18bf..0000000 Binary files a/TestImages/reference_alpha/mox_jet.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mox_pearl.jpg b/TestImages/reference_alpha/mox_pearl.jpg deleted file mode 100644 index 97d12ee..0000000 Binary files a/TestImages/reference_alpha/mox_pearl.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mox_ruby.jpg b/TestImages/reference_alpha/mox_ruby.jpg deleted file mode 100644 index c2d1d3b..0000000 Binary files a/TestImages/reference_alpha/mox_ruby.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/mox_sapphire.jpg b/TestImages/reference_alpha/mox_sapphire.jpg deleted file mode 100644 index ed7e87e..0000000 Binary files a/TestImages/reference_alpha/mox_sapphire.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/nightmare.jpg b/TestImages/reference_alpha/nightmare.jpg deleted file mode 100644 index d1a0a15..0000000 Binary files a/TestImages/reference_alpha/nightmare.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/plateau.jpg b/TestImages/reference_alpha/plateau.jpg deleted file mode 100644 index 0d5ccd5..0000000 Binary files a/TestImages/reference_alpha/plateau.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/regrowth.jpg b/TestImages/reference_alpha/regrowth.jpg deleted file mode 100644 index 97fd879..0000000 Binary files a/TestImages/reference_alpha/regrowth.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/rock_hydra.jpg b/TestImages/reference_alpha/rock_hydra.jpg deleted file mode 100644 index b88b8c5..0000000 Binary files a/TestImages/reference_alpha/rock_hydra.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/royal_assassin.jpg b/TestImages/reference_alpha/royal_assassin.jpg deleted file mode 100644 index fa23a71..0000000 Binary files a/TestImages/reference_alpha/royal_assassin.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/savannah.jpg b/TestImages/reference_alpha/savannah.jpg deleted file mode 100644 index 2ef8dd9..0000000 Binary files a/TestImages/reference_alpha/savannah.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/scrubland.jpg b/TestImages/reference_alpha/scrubland.jpg deleted file mode 100644 index bfaf8b8..0000000 Binary files a/TestImages/reference_alpha/scrubland.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/serra_angel.jpg b/TestImages/reference_alpha/serra_angel.jpg deleted file mode 100644 index 7bc59cf..0000000 Binary files a/TestImages/reference_alpha/serra_angel.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/shivan_dragon.jpg b/TestImages/reference_alpha/shivan_dragon.jpg deleted file mode 100644 index 3126461..0000000 Binary files a/TestImages/reference_alpha/shivan_dragon.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/sol_ring.jpg b/TestImages/reference_alpha/sol_ring.jpg deleted file mode 100644 index a754249..0000000 Binary files a/TestImages/reference_alpha/sol_ring.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/swords_to_plowshares.jpg b/TestImages/reference_alpha/swords_to_plowshares.jpg deleted file mode 100644 index 964667e..0000000 Binary files a/TestImages/reference_alpha/swords_to_plowshares.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/taiga.jpg b/TestImages/reference_alpha/taiga.jpg deleted file mode 100644 index a9465b7..0000000 Binary files a/TestImages/reference_alpha/taiga.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/time_walk.jpg b/TestImages/reference_alpha/time_walk.jpg deleted file mode 100644 index 0807e9a..0000000 Binary files a/TestImages/reference_alpha/time_walk.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/timetwister.jpg b/TestImages/reference_alpha/timetwister.jpg deleted file mode 100644 index aa95c55..0000000 Binary files a/TestImages/reference_alpha/timetwister.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/tropical_island.jpg b/TestImages/reference_alpha/tropical_island.jpg deleted file mode 100644 index 186a951..0000000 Binary files a/TestImages/reference_alpha/tropical_island.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/tundra.jpg b/TestImages/reference_alpha/tundra.jpg deleted file mode 100644 index d2769bc..0000000 Binary files a/TestImages/reference_alpha/tundra.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/underground_sea.jpg b/TestImages/reference_alpha/underground_sea.jpg deleted file mode 100644 index 6824628..0000000 Binary files a/TestImages/reference_alpha/underground_sea.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/wheel_of_fortune.jpg b/TestImages/reference_alpha/wheel_of_fortune.jpg deleted file mode 100644 index 603136f..0000000 Binary files a/TestImages/reference_alpha/wheel_of_fortune.jpg and /dev/null differ diff --git a/TestImages/reference_alpha/wrath_of_god.jpg b/TestImages/reference_alpha/wrath_of_god.jpg deleted file mode 100644 index 9339812..0000000 Binary files a/TestImages/reference_alpha/wrath_of_god.jpg and /dev/null differ diff --git a/TestImages/single_cards/adanto_vanguard.png b/TestImages/single_cards/adanto_vanguard.png deleted file mode 100644 index a7d27c2..0000000 Binary files a/TestImages/single_cards/adanto_vanguard.png and /dev/null differ diff --git a/TestImages/single_cards/angel_of_sanctions.png b/TestImages/single_cards/angel_of_sanctions.png deleted file mode 100644 index 181ed0b..0000000 Binary files a/TestImages/single_cards/angel_of_sanctions.png and /dev/null differ diff --git a/TestImages/single_cards/attunement.jpg b/TestImages/single_cards/attunement.jpg deleted file mode 100644 index 5994502..0000000 Binary files a/TestImages/single_cards/attunement.jpg and /dev/null differ diff --git a/TestImages/single_cards/avaricious_dragon.jpg b/TestImages/single_cards/avaricious_dragon.jpg deleted file mode 100644 index 396fa6c..0000000 Binary files a/TestImages/single_cards/avaricious_dragon.jpg and /dev/null differ diff --git a/TestImages/single_cards/burgeoning.png b/TestImages/single_cards/burgeoning.png deleted file mode 100644 index 0a5baba..0000000 Binary files a/TestImages/single_cards/burgeoning.png and /dev/null differ diff --git a/TestImages/single_cards/fireball.jpg b/TestImages/single_cards/fireball.jpg deleted file mode 100644 index 1a6a56f..0000000 Binary files a/TestImages/single_cards/fireball.jpg and /dev/null differ diff --git a/TestImages/single_cards/jarad_golgari.jpg b/TestImages/single_cards/jarad_golgari.jpg deleted file mode 100644 index ee26e77..0000000 Binary files a/TestImages/single_cards/jarad_golgari.jpg and /dev/null differ diff --git a/TestImages/single_cards/llanowar_elves.jpg b/TestImages/single_cards/llanowar_elves.jpg deleted file mode 100644 index 33adb4b..0000000 Binary files a/TestImages/single_cards/llanowar_elves.jpg and /dev/null differ diff --git a/TestImages/single_cards/meletis_charlatan.jpg b/TestImages/single_cards/meletis_charlatan.jpg deleted file mode 100644 index 8c736f7..0000000 Binary files a/TestImages/single_cards/meletis_charlatan.jpg and /dev/null differ diff --git a/TestImages/single_cards/mindstab_thrull.jpeg b/TestImages/single_cards/mindstab_thrull.jpeg deleted file mode 100644 index 95b1c61..0000000 Binary files a/TestImages/single_cards/mindstab_thrull.jpeg and /dev/null differ diff --git a/TestImages/single_cards/pacifism.jpg b/TestImages/single_cards/pacifism.jpg deleted file mode 100644 index 7ed4f88..0000000 Binary files a/TestImages/single_cards/pacifism.jpg and /dev/null differ diff --git a/TestImages/single_cards/platinum_angel.jpg b/TestImages/single_cards/platinum_angel.jpg deleted file mode 100644 index b971461..0000000 Binary files a/TestImages/single_cards/platinum_angel.jpg and /dev/null differ diff --git a/TestImages/single_cards/queen_marchesa.png b/TestImages/single_cards/queen_marchesa.png deleted file mode 100644 index aa2b3f7..0000000 Binary files a/TestImages/single_cards/queen_marchesa.png and /dev/null differ diff --git a/TestImages/single_cards/queen_marchesa_analyzer.png b/TestImages/single_cards/queen_marchesa_analyzer.png deleted file mode 100644 index aa2b3f7..0000000 Binary files a/TestImages/single_cards/queen_marchesa_analyzer.png and /dev/null differ diff --git a/TestImages/single_cards/shivan_dragon.jpg b/TestImages/single_cards/shivan_dragon.jpg deleted file mode 100644 index 50276a1..0000000 Binary files a/TestImages/single_cards/shivan_dragon.jpg and /dev/null differ diff --git a/TestImages/single_cards/spellseeker.png b/TestImages/single_cards/spellseeker.png deleted file mode 100644 index 0a3cb75..0000000 Binary files a/TestImages/single_cards/spellseeker.png and /dev/null differ diff --git a/TestImages/single_cards/tarmogoyf.jpg b/TestImages/single_cards/tarmogoyf.jpg deleted file mode 100644 index e547a94..0000000 Binary files a/TestImages/single_cards/tarmogoyf.jpg and /dev/null differ diff --git a/TestImages/single_cards/thought_reflection.jpg b/TestImages/single_cards/thought_reflection.jpg deleted file mode 100644 index e1c7ba5..0000000 Binary files a/TestImages/single_cards/thought_reflection.jpg and /dev/null differ diff --git a/TestImages/single_cards/unsummon.jpg b/TestImages/single_cards/unsummon.jpg deleted file mode 100644 index a44be04..0000000 Binary files a/TestImages/single_cards/unsummon.jpg and /dev/null differ diff --git a/TestImages/tokens/angel_token_alter.jpg b/TestImages/tokens/angel_token_alter.jpg deleted file mode 100644 index 8a94cae..0000000 Binary files a/TestImages/tokens/angel_token_alter.jpg and /dev/null differ diff --git a/TestImages/tokens/brothers_tokens.jpg b/TestImages/tokens/brothers_tokens.jpg deleted file mode 100644 index f3363d3..0000000 Binary files a/TestImages/tokens/brothers_tokens.jpg and /dev/null differ diff --git a/TestImages/tokens/christopher_rush_tokens.jpg b/TestImages/tokens/christopher_rush_tokens.jpg deleted file mode 100644 index bc93444..0000000 Binary files a/TestImages/tokens/christopher_rush_tokens.jpg and /dev/null differ diff --git a/TestImages/tokens/custom_tokens.jpg b/TestImages/tokens/custom_tokens.jpg deleted file mode 100644 index 89d4dda..0000000 Binary files a/TestImages/tokens/custom_tokens.jpg and /dev/null differ diff --git a/TestImages/tokens/elspeth_emblem.jpg b/TestImages/tokens/elspeth_emblem.jpg deleted file mode 100644 index 78be8d9..0000000 Binary files a/TestImages/tokens/elspeth_emblem.jpg and /dev/null differ diff --git a/TestImages/tokens/elspeth_starwars_emblem.jpg b/TestImages/tokens/elspeth_starwars_emblem.jpg deleted file mode 100644 index d37ba4d..0000000 Binary files a/TestImages/tokens/elspeth_starwars_emblem.jpg and /dev/null differ diff --git a/TestImages/tokens/gideon_emblem.jpg b/TestImages/tokens/gideon_emblem.jpg deleted file mode 100644 index a9292d3..0000000 Binary files a/TestImages/tokens/gideon_emblem.jpg and /dev/null differ diff --git a/TestImages/tokens/narset_emblem.jpg b/TestImages/tokens/narset_emblem.jpg deleted file mode 100644 index 5b2c0fc..0000000 Binary files a/TestImages/tokens/narset_emblem.jpg and /dev/null differ diff --git a/TestImages/tokens/ratadrabik_token.jpg b/TestImages/tokens/ratadrabik_token.jpg deleted file mode 100644 index 9a10a4f..0000000 Binary files a/TestImages/tokens/ratadrabik_token.jpg and /dev/null differ diff --git a/TestImages/tokens/rkpost_rhino_tokens.jpg b/TestImages/tokens/rkpost_rhino_tokens.jpg deleted file mode 100644 index 34ccd1b..0000000 Binary files a/TestImages/tokens/rkpost_rhino_tokens.jpg and /dev/null differ diff --git a/TestImages/tokens/token_collection_pucatrade.jpg b/TestImages/tokens/token_collection_pucatrade.jpg deleted file mode 100644 index 4297869..0000000 Binary files a/TestImages/tokens/token_collection_pucatrade.jpg and /dev/null differ diff --git a/TestImages/tokens/tokens_foils_lands.jpg b/TestImages/tokens/tokens_foils_lands.jpg deleted file mode 100644 index 850bd60..0000000 Binary files a/TestImages/tokens/tokens_foils_lands.jpg and /dev/null differ diff --git a/TestImages/tokens/vampire_knight_token.jpg b/TestImages/tokens/vampire_knight_token.jpg deleted file mode 100644 index 5649e7e..0000000 Binary files a/TestImages/tokens/vampire_knight_token.jpg and /dev/null differ diff --git a/TestImages/training_examples/training_set_1.jpg b/TestImages/training_examples/training_set_1.jpg deleted file mode 100644 index b3d4ffe..0000000 Binary files a/TestImages/training_examples/training_set_1.jpg and /dev/null differ diff --git a/TestImages/training_examples/training_set_2.jpg b/TestImages/training_examples/training_set_2.jpg deleted file mode 100644 index 32bd556..0000000 Binary files a/TestImages/training_examples/training_set_2.jpg and /dev/null differ diff --git a/TestImages/training_examples/training_set_3.jpg b/TestImages/training_examples/training_set_3.jpg deleted file mode 100644 index 8467af5..0000000 Binary files a/TestImages/training_examples/training_set_3.jpg and /dev/null differ diff --git a/TestImages/varying_quality/black.jpg b/TestImages/varying_quality/black.jpg deleted file mode 100644 index dc90cae..0000000 Binary files a/TestImages/varying_quality/black.jpg and /dev/null differ diff --git a/TestImages/varying_quality/card_in_plastic_case.jpg b/TestImages/varying_quality/card_in_plastic_case.jpg deleted file mode 100644 index e771a5c..0000000 Binary files a/TestImages/varying_quality/card_in_plastic_case.jpg and /dev/null differ diff --git a/TestImages/varying_quality/counterspell_bgs.jpg b/TestImages/varying_quality/counterspell_bgs.jpg deleted file mode 100644 index 25a8e1c..0000000 Binary files a/TestImages/varying_quality/counterspell_bgs.jpg and /dev/null differ diff --git a/TestImages/varying_quality/dragon_whelp.jpg b/TestImages/varying_quality/dragon_whelp.jpg deleted file mode 100644 index effdde6..0000000 Binary files a/TestImages/varying_quality/dragon_whelp.jpg and /dev/null differ diff --git a/TestImages/varying_quality/evil_eye.jpg b/TestImages/varying_quality/evil_eye.jpg deleted file mode 100644 index faad74e..0000000 Binary files a/TestImages/varying_quality/evil_eye.jpg and /dev/null differ diff --git a/TestImages/varying_quality/frilly.jpg b/TestImages/varying_quality/frilly.jpg deleted file mode 100644 index 5ab39fd..0000000 Binary files a/TestImages/varying_quality/frilly.jpg and /dev/null differ diff --git a/TestImages/varying_quality/image_orig.jpg b/TestImages/varying_quality/image_orig.jpg deleted file mode 100644 index 440ad18..0000000 Binary files a/TestImages/varying_quality/image_orig.jpg and /dev/null differ diff --git a/TestImages/varying_quality/instill_energy.jpg b/TestImages/varying_quality/instill_energy.jpg deleted file mode 100644 index c443961..0000000 Binary files a/TestImages/varying_quality/instill_energy.jpg and /dev/null differ diff --git a/TestImages/varying_quality/ruby.jpg b/TestImages/varying_quality/ruby.jpg deleted file mode 100644 index a343232..0000000 Binary files a/TestImages/varying_quality/ruby.jpg and /dev/null differ diff --git a/TestImages/varying_quality/s-l300.jpg b/TestImages/varying_quality/s-l300.jpg deleted file mode 100644 index 819daca..0000000 Binary files a/TestImages/varying_quality/s-l300.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test.jpg b/TestImages/varying_quality/test.jpg deleted file mode 100644 index 233ffa8..0000000 Binary files a/TestImages/varying_quality/test.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test1.jpg b/TestImages/varying_quality/test1.jpg deleted file mode 100644 index a75278e..0000000 Binary files a/TestImages/varying_quality/test1.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test10.jpg b/TestImages/varying_quality/test10.jpg deleted file mode 100644 index 8e9062b..0000000 Binary files a/TestImages/varying_quality/test10.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test11.jpg b/TestImages/varying_quality/test11.jpg deleted file mode 100644 index b0795f4..0000000 Binary files a/TestImages/varying_quality/test11.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test12.jpg b/TestImages/varying_quality/test12.jpg deleted file mode 100644 index c2f5de6..0000000 Binary files a/TestImages/varying_quality/test12.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test13.jpg b/TestImages/varying_quality/test13.jpg deleted file mode 100644 index 878cbad..0000000 Binary files a/TestImages/varying_quality/test13.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test14.jpg b/TestImages/varying_quality/test14.jpg deleted file mode 100644 index bf5094a..0000000 Binary files a/TestImages/varying_quality/test14.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test15.jpg b/TestImages/varying_quality/test15.jpg deleted file mode 100644 index 39f1dd4..0000000 Binary files a/TestImages/varying_quality/test15.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test16.jpg b/TestImages/varying_quality/test16.jpg deleted file mode 100644 index c514771..0000000 Binary files a/TestImages/varying_quality/test16.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test17.jpg b/TestImages/varying_quality/test17.jpg deleted file mode 100644 index 4ad12f7..0000000 Binary files a/TestImages/varying_quality/test17.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test18.jpg b/TestImages/varying_quality/test18.jpg deleted file mode 100644 index a0f9390..0000000 Binary files a/TestImages/varying_quality/test18.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test19.jpg b/TestImages/varying_quality/test19.jpg deleted file mode 100644 index 8f3c5a6..0000000 Binary files a/TestImages/varying_quality/test19.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test2.jpg b/TestImages/varying_quality/test2.jpg deleted file mode 100644 index 1fceb1f..0000000 Binary files a/TestImages/varying_quality/test2.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test20.jpg b/TestImages/varying_quality/test20.jpg deleted file mode 100644 index 8717d5f..0000000 Binary files a/TestImages/varying_quality/test20.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test21.jpg b/TestImages/varying_quality/test21.jpg deleted file mode 100644 index 342577c..0000000 Binary files a/TestImages/varying_quality/test21.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test22.png b/TestImages/varying_quality/test22.png deleted file mode 100644 index 179f188..0000000 Binary files a/TestImages/varying_quality/test22.png and /dev/null differ diff --git a/TestImages/varying_quality/test23.jpg b/TestImages/varying_quality/test23.jpg deleted file mode 100644 index af79a6f..0000000 Binary files a/TestImages/varying_quality/test23.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test24.jpg b/TestImages/varying_quality/test24.jpg deleted file mode 100644 index 937354c..0000000 Binary files a/TestImages/varying_quality/test24.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test25.jpg b/TestImages/varying_quality/test25.jpg deleted file mode 100644 index 6e39077..0000000 Binary files a/TestImages/varying_quality/test25.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test26.jpg b/TestImages/varying_quality/test26.jpg deleted file mode 100644 index ee83759..0000000 Binary files a/TestImages/varying_quality/test26.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test27.jpg b/TestImages/varying_quality/test27.jpg deleted file mode 100644 index 0ee79be..0000000 Binary files a/TestImages/varying_quality/test27.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test3.jpg b/TestImages/varying_quality/test3.jpg deleted file mode 100644 index fd1f2cb..0000000 Binary files a/TestImages/varying_quality/test3.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test4.jpg b/TestImages/varying_quality/test4.jpg deleted file mode 100644 index 1f2ffc6..0000000 Binary files a/TestImages/varying_quality/test4.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test5.jpg b/TestImages/varying_quality/test5.jpg deleted file mode 100644 index f9e8a1f..0000000 Binary files a/TestImages/varying_quality/test5.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test6.jpg b/TestImages/varying_quality/test6.jpg deleted file mode 100644 index 1454673..0000000 Binary files a/TestImages/varying_quality/test6.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test7.jpg b/TestImages/varying_quality/test7.jpg deleted file mode 100644 index 82dfb3c..0000000 Binary files a/TestImages/varying_quality/test7.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test8.jpg b/TestImages/varying_quality/test8.jpg deleted file mode 100644 index 2d480ce..0000000 Binary files a/TestImages/varying_quality/test8.jpg and /dev/null differ diff --git a/TestImages/varying_quality/test9.jpg b/TestImages/varying_quality/test9.jpg deleted file mode 100644 index c8b0f53..0000000 Binary files a/TestImages/varying_quality/test9.jpg and /dev/null differ diff --git a/TestImages/worn/bent_creased.jpg b/TestImages/worn/bent_creased.jpg deleted file mode 100644 index 18c948a..0000000 Binary files a/TestImages/worn/bent_creased.jpg and /dev/null differ diff --git a/TestImages/worn/edge_nick.png b/TestImages/worn/edge_nick.png deleted file mode 100644 index 68a7251..0000000 Binary files a/TestImages/worn/edge_nick.png and /dev/null differ diff --git a/TestImages/worn/edge_white.png b/TestImages/worn/edge_white.png deleted file mode 100644 index 1c91723..0000000 Binary files a/TestImages/worn/edge_white.png and /dev/null differ diff --git a/TestImages/worn/good_1.jpg b/TestImages/worn/good_1.jpg deleted file mode 100644 index cd0007e..0000000 Binary files a/TestImages/worn/good_1.jpg and /dev/null differ diff --git a/TestImages/worn/good_2.jpg b/TestImages/worn/good_2.jpg deleted file mode 100644 index bd6e04e..0000000 Binary files a/TestImages/worn/good_2.jpg and /dev/null differ diff --git a/TestImages/worn/hp_binder_bite_back.webp b/TestImages/worn/hp_binder_bite_back.webp deleted file mode 100644 index 727f380..0000000 Binary files a/TestImages/worn/hp_binder_bite_back.webp and /dev/null differ diff --git a/TestImages/worn/hp_binder_bite_front.webp b/TestImages/worn/hp_binder_bite_front.webp deleted file mode 100644 index 936ce8d..0000000 Binary files a/TestImages/worn/hp_binder_bite_front.webp and /dev/null differ diff --git a/TestImages/worn/hp_compromised_corner.webp b/TestImages/worn/hp_compromised_corner.webp deleted file mode 100644 index 8665a6d..0000000 Binary files a/TestImages/worn/hp_compromised_corner.webp and /dev/null differ diff --git a/TestImages/worn/hp_scratches.png b/TestImages/worn/hp_scratches.png deleted file mode 100644 index b179f72..0000000 Binary files a/TestImages/worn/hp_scratches.png and /dev/null differ diff --git a/TestImages/worn/hp_shuffle_crease.webp b/TestImages/worn/hp_shuffle_crease.webp deleted file mode 100644 index 6ad1542..0000000 Binary files a/TestImages/worn/hp_shuffle_crease.webp and /dev/null differ diff --git a/TestImages/worn/hp_water_warping.png b/TestImages/worn/hp_water_warping.png deleted file mode 100644 index 590dfc0..0000000 Binary files a/TestImages/worn/hp_water_warping.png and /dev/null differ diff --git a/TestImages/worn/scratch.png b/TestImages/worn/scratch.png deleted file mode 100644 index d7830d6..0000000 Binary files a/TestImages/worn/scratch.png and /dev/null differ diff --git a/TestImages/worn/spotting.png b/TestImages/worn/spotting.png deleted file mode 100644 index f559c42..0000000 Binary files a/TestImages/worn/spotting.png and /dev/null differ diff --git a/TestImages/worn/very_good_1.jpg b/TestImages/worn/very_good_1.jpg deleted file mode 100644 index 938cd43..0000000 Binary files a/TestImages/worn/very_good_1.jpg and /dev/null differ diff --git a/TestImages/worn/very_good_2.jpg b/TestImages/worn/very_good_2.jpg deleted file mode 100644 index 2431c08..0000000 Binary files a/TestImages/worn/very_good_2.jpg and /dev/null differ diff --git a/app.json b/app.json deleted file mode 100644 index 91820a9..0000000 --- a/app.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "expo": { - "name": "Scry", - "slug": "scry", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/images/icon.png", - "scheme": "scry", - "userInterfaceStyle": "automatic", - "newArchEnabled": true, - "splash": { - "image": "./assets/images/splash-icon.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/images/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "edgeToEdgeEnabled": true, - "predictiveBackGestureEnabled": false - }, - "web": { - "bundler": "metro", - "output": "static", - "favicon": "./assets/images/favicon.png" - }, - "plugins": [ - "expo-router", - [ - "expo-media-library", - { - "photosPermission": "Allow Scry to save debug images to your photo library.", - "savePhotosPermission": "Allow Scry to save debug images to your photo library." - } - ] - ], - "experiments": { - "typedRoutes": true - } - } -} diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx deleted file mode 100644 index 8454c7d..0000000 --- a/app/(tabs)/_layout.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import FontAwesome from "@expo/vector-icons/FontAwesome"; -import { Tabs } from "expo-router"; -import { useColorScheme } from "@/components/useColorScheme"; -import Colors from "@/constants/Colors"; - -function TabBarIcon(props: { - name: React.ComponentProps["name"]; - color: string; -}) { - return ; -} - -export default function TabLayout() { - const colorScheme = useColorScheme(); - - return ( - - ( - - ), - }} - /> - , - }} - /> - , - }} - /> - - ); -} diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx deleted file mode 100644 index 3219241..0000000 --- a/app/(tabs)/index.tsx +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Collection screen - displays user's scanned cards. - */ - -import React, { useState, useCallback, useMemo } from "react"; -import { - StyleSheet, - View, - Text, - FlatList, - Image, - Pressable, - TextInput, - RefreshControl, - Dimensions, - ActivityIndicator, -} from "react-native"; -import { FontAwesome } from "@expo/vector-icons"; -import { useQuery } from "convex/react"; -import { useRouter } from "expo-router"; -import { api } from "../../convex/_generated/api"; -import { useCurrentUser } from "@/lib/hooks"; - -const { width: SCREEN_WIDTH } = Dimensions.get("window"); -const CARD_WIDTH = (SCREEN_WIDTH - 48) / 3; -const CARD_HEIGHT = CARD_WIDTH * (88 / 63); // MTG aspect ratio - -interface CollectionItem { - id: string; - cardId: string; - name: string; - setCode: string; - imageUri?: string; - quantity: number; - isFoil: boolean; - addedAt: number; -} - -export default function CollectionScreen() { - const router = useRouter(); - const [searchQuery, setSearchQuery] = useState(""); - const [refreshing, setRefreshing] = useState(false); - const [sortBy, setSortBy] = useState<"name" | "set" | "recent">("recent"); - - // Get authenticated user - const { user, isAuthenticated } = useCurrentUser(); - const userId = user?._id ?? null; - - // Fetch collection from Convex - const rawCollection = useQuery( - api.collections.getByUser, - userId ? { userId: userId as any } : "skip" - ); - - // Transform Convex data to UI format - const collection = useMemo(() => { - if (!rawCollection) return []; - - return rawCollection.map((entry) => ({ - id: entry._id, - cardId: entry.cardId, - name: entry.card?.name || "Unknown", - setCode: entry.card?.setCode || "???", - imageUri: entry.card?.imageUri, - quantity: entry.quantity, - isFoil: entry.isFoil, - addedAt: entry.addedAt, - })); - }, [rawCollection]); - - // Filter collection by search query - const filteredCollection = useMemo( - () => - collection.filter((item) => - item.name.toLowerCase().includes(searchQuery.toLowerCase()) - ), - [collection, searchQuery] - ); - - // Sort collection - const sortedCollection = useMemo( - () => - [...filteredCollection].sort((a, b) => { - switch (sortBy) { - case "name": - return a.name.localeCompare(b.name); - case "set": - return a.setCode.localeCompare(b.setCode); - case "recent": - default: - return b.addedAt - a.addedAt; - } - }), - [filteredCollection, sortBy] - ); - - const onRefresh = useCallback(async () => { - setRefreshing(true); - // Convex automatically syncs - just show loading briefly - await new Promise((resolve) => setTimeout(resolve, 500)); - setRefreshing(false); - }, []); - - const renderCard = useCallback( - ({ item }: { item: CollectionItem }) => ( - - router.push({ - pathname: "/modal", - params: { - collectionEntryId: item.id, - cardId: item.cardId, - }, - }) - } - > - {item.imageUri ? ( - - ) : ( - - - - )} - {item.quantity > 1 && ( - - ×{item.quantity} - - )} - {item.isFoil && ( - - - - )} - - ), - [router] - ); - - const renderEmpty = useCallback( - () => ( - - - No Cards Yet - - {userId - ? "Start scanning cards to build your collection!" - : "Sign in to start building your collection!"} - - - ), - [userId] - ); - - // Loading state - if (rawCollection === undefined && userId) { - return ( - - - Loading collection... - - ); - } - - // Total cards count - const totalCount = collection.reduce((sum, item) => sum + item.quantity, 0); - - return ( - - {/* Search bar */} - - - - {searchQuery.length > 0 && ( - setSearchQuery("")}> - - - )} - - - {/* Stats and sort bar */} - - - {sortedCollection.length} unique - {collection.length > 0 && ` (${totalCount} total)`} - - - - setSortBy("recent")} - > - - Recent - - - setSortBy("name")} - > - - Name - - - setSortBy("set")} - > - - Set - - - - - - {/* Card grid */} - item.id} - numColumns={3} - contentContainerStyle={styles.gridContent} - ListEmptyComponent={renderEmpty} - refreshControl={ - - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#1a1a1a", - }, - searchContainer: { - flexDirection: "row", - alignItems: "center", - backgroundColor: "#2a2a2a", - marginHorizontal: 16, - marginTop: 16, - marginBottom: 8, - paddingHorizontal: 12, - borderRadius: 10, - }, - searchIcon: { - marginRight: 8, - }, - searchInput: { - flex: 1, - height: 44, - color: "#fff", - fontSize: 16, - }, - statsBar: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - paddingHorizontal: 16, - paddingVertical: 8, - }, - statsText: { - color: "#888", - fontSize: 14, - }, - sortButtons: { - flexDirection: "row", - gap: 4, - }, - sortButton: { - paddingHorizontal: 10, - paddingVertical: 6, - borderRadius: 6, - }, - sortButtonActive: { - backgroundColor: "#333", - }, - sortButtonText: { - color: "#666", - fontSize: 12, - fontWeight: "500", - }, - sortButtonTextActive: { - color: "#fff", - }, - gridContent: { - padding: 16, - paddingTop: 8, - }, - cardContainer: { - width: CARD_WIDTH, - height: CARD_HEIGHT, - marginRight: 8, - marginBottom: 8, - borderRadius: 8, - overflow: "hidden", - }, - cardImage: { - width: "100%", - height: "100%", - backgroundColor: "#2a2a2a", - borderRadius: 8, - }, - cardPlaceholder: { - justifyContent: "center", - alignItems: "center", - }, - quantityBadge: { - position: "absolute", - top: 4, - right: 4, - backgroundColor: "rgba(0,0,0,0.8)", - paddingHorizontal: 6, - paddingVertical: 2, - borderRadius: 4, - }, - quantityText: { - color: "#fff", - fontSize: 11, - fontWeight: "600", - }, - foilBadge: { - position: "absolute", - top: 4, - left: 4, - backgroundColor: "rgba(0,0,0,0.8)", - padding: 4, - borderRadius: 4, - }, - emptyContainer: { - flex: 1, - justifyContent: "center", - alignItems: "center", - paddingTop: 100, - paddingHorizontal: 40, - }, - emptyTitle: { - marginTop: 20, - fontSize: 22, - fontWeight: "bold", - color: "#fff", - }, - emptyText: { - marginTop: 8, - fontSize: 16, - color: "#888", - textAlign: "center", - }, - loadingContainer: { - flex: 1, - justifyContent: "center", - alignItems: "center", - backgroundColor: "#1a1a1a", - }, - loadingText: { - marginTop: 16, - color: "#888", - fontSize: 16, - }, -}); diff --git a/app/(tabs)/scan.tsx b/app/(tabs)/scan.tsx deleted file mode 100644 index b7ab201..0000000 --- a/app/(tabs)/scan.tsx +++ /dev/null @@ -1,591 +0,0 @@ -/** - * Camera scanning screen for card recognition. - */ - -import React, { useCallback, useRef, useState, useEffect } from "react"; -import { - StyleSheet, - View, - Text, - Pressable, - ActivityIndicator, - Dimensions, - Image, - Alert, -} from "react-native"; -import { FontAwesome } from "@expo/vector-icons"; -import { useMutation, useQuery } from "convex/react"; -import { api } from "../../convex/_generated/api"; -import { useCameraPermission } from "@/lib/hooks/useCamera"; -import { useCurrentUser } from "@/lib/hooks"; -import { useHashCache } from "@/lib/context"; -import { recognizeCard, configureDebug } from "@/lib/recognition"; -import { loadImageAsBase64 } from "@/lib/recognition/imageLoader"; -import { decodeImageBase64 } from "@/lib/recognition/skiaDecoder"; -import { AdaptiveCamera, CameraHandle, isExpoGo } from "@/components/camera"; - -const { height: SCREEN_HEIGHT } = Dimensions.get("window"); -const CARD_ASPECT_RATIO = 63 / 88; // Width / Height -const SCAN_BOX_HEIGHT = SCREEN_HEIGHT * 0.5; -const SCAN_BOX_WIDTH = SCAN_BOX_HEIGHT * CARD_ASPECT_RATIO; - -interface ScanResult { - cardId: string; - cardName: string; - setCode: string; - imageUri?: string; - confidence: number; - distance: number; - timestamp: number; -} - -export default function ScanScreen() { - const { hasPermission, isLoading: permLoading, requestPermission } = useCameraPermission(); - const cameraRef = useRef(null); - - // Local scan state - const [isProcessing, setIsProcessing] = useState(false); - const [flashEnabled, setFlashEnabled] = useState(false); - const [isAddingToCollection, setIsAddingToCollection] = useState(false); - const [lastScanResult, setLastScanResult] = useState(null); - const [scanError, setScanError] = useState(null); - const [debugEnabled, setDebugEnabled] = useState(false); - - // Hash cache from context - const { cardHashes, hashesLoaded } = useHashCache(); - - // Get authenticated user - const { user, isAuthenticated } = useCurrentUser(); - const userId = user?._id ?? null; - - // Convex mutations - const addToCollection = useMutation(api.collections.add); - const getCardByScryfallId = useQuery( - api.cards.getByScryfallId, - lastScanResult ? { scryfallId: lastScanResult.cardId } : "skip" - ); - - // Toggle debug mode - const toggleDebug = useCallback(() => { - const newState = !debugEnabled; - setDebugEnabled(newState); - configureDebug({ - enabled: newState, - albumName: "Scry Debug", - }); - console.log("[Scry] Debug mode:", newState ? "ON" : "OFF"); - }, [debugEnabled]); - - const clearScanState = useCallback(() => { - setLastScanResult(null); - setScanError(null); - }, []); - - // Auto-clear scan result after 5 seconds - useEffect(() => { - if (lastScanResult) { - const timer = setTimeout(() => { - clearScanState(); - }, 5000); - return () => clearTimeout(timer); - } - }, [lastScanResult, clearScanState]); - - const handleCapture = useCallback(async () => { - if (!cameraRef.current || isProcessing || !hashesLoaded) return; - - setIsProcessing(true); - - try { - // Take photo using adaptive camera - const photo = await cameraRef.current.takePhoto(); - - console.log("[Scry] Photo captured:", photo.uri); - - // Load and resize image for processing - const { base64 } = await loadImageAsBase64( - photo.uri, - 480, // Target width for processing - 640 // Target height - ); - - if (!base64) { - throw new Error("Failed to load image data"); - } - - // Decode to RGBA pixels using Skia - const decoded = decodeImageBase64(base64); - - if (!decoded) { - throw new Error("Failed to decode image pixels"); - } - - console.log("[Scry] Image decoded:", decoded.width, "x", decoded.height); - console.log("[Scry] Matching against", cardHashes.length, "cards"); - - // Run recognition - const result = recognizeCard( - decoded.pixels, - decoded.width, - decoded.height, - cardHashes, - { - enableCardDetection: true, - enableRotationMatching: true, - minConfidence: 0.85, - debug: debugEnabled, - } - ); - - console.log("[Scry] Recognition result:", result); - - if (result.success && result.match) { - // Find the card info from our hashes - const matchedCard = cardHashes.find((c) => c.id === result.match!.cardId); - - setLastScanResult({ - cardId: result.match.cardId, - cardName: matchedCard?.name || "Unknown Card", - setCode: matchedCard?.setCode || "???", - imageUri: matchedCard?.imageUri, - confidence: result.match.confidence, - distance: result.match.distance, - timestamp: Date.now(), - }); - setScanError(null); - } else { - setScanError(result.error || "No match found. Try adjusting lighting or angle."); - setLastScanResult(null); - } - } catch (error) { - console.error("[Scry] Capture error:", error); - setScanError(error instanceof Error ? error.message : "Failed to capture image"); - setLastScanResult(null); - } finally { - setIsProcessing(false); - } - }, [isProcessing, hashesLoaded, cardHashes, debugEnabled]); - - const handleAddToCollection = useCallback(async () => { - if (!lastScanResult || !userId || !getCardByScryfallId) return; - - setIsAddingToCollection(true); - - try { - await addToCollection({ - userId: userId as any, - cardId: getCardByScryfallId._id, - quantity: 1, - isFoil: false, - }); - - Alert.alert( - "Added to Collection", - `${lastScanResult.cardName} has been added to your collection.`, - [{ text: "OK" }] - ); - - clearScanState(); - } catch (error) { - console.error("[Scry] Failed to add to collection:", error); - Alert.alert("Error", "Failed to add card to collection. Please try again.", [ - { text: "OK" }, - ]); - } finally { - setIsAddingToCollection(false); - } - }, [lastScanResult, userId, getCardByScryfallId, addToCollection, clearScanState]); - - // Loading state - if (permLoading) { - return ( - - - Checking camera permission... - - ); - } - - // Permission denied - if (!hasPermission) { - return ( - - - Camera Access Required - - Scry needs camera access to scan your Magic cards. - - - Enable Camera - - - ); - } - - return ( - - {/* Adaptive camera - uses expo-camera in Expo Go, Vision Camera in production */} - - - {/* Overlay with scan box */} - - {/* Top dark area */} - - - {/* Middle row with scan box */} - - - - {/* Corner markers */} - - - - - - - - - {/* Bottom dark area */} - - - - {/* Instructions */} - - - {hashesLoaded - ? `Position card in frame • ${cardHashes.length} cards loaded` - : "Loading card database..."} - - {isExpoGo && ( - Dev mode (expo-camera) - )} - - - {/* Scan result overlay */} - {lastScanResult && ( - - - {lastScanResult.imageUri && ( - - )} - - {lastScanResult.cardName} - {lastScanResult.setCode.toUpperCase()} - - {Math.round(lastScanResult.confidence * 100)}% match - - - - {isAddingToCollection ? ( - - ) : ( - <> - - Add - - )} - - - - )} - - {/* Error message */} - {scanError && ( - - {scanError} - - Dismiss - - - )} - - {/* Controls */} - - {/* Flash toggle */} - setFlashEnabled(!flashEnabled)}> - - - - {/* Capture button */} - - {isProcessing ? ( - - ) : ( - - )} - - - {/* Debug toggle */} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#000", - }, - centered: { - flex: 1, - justifyContent: "center", - alignItems: "center", - backgroundColor: "#1a1a1a", - padding: 20, - }, - loadingText: { - marginTop: 16, - color: "#888", - fontSize: 16, - }, - permissionTitle: { - marginTop: 20, - fontSize: 24, - fontWeight: "bold", - color: "#fff", - }, - permissionText: { - marginTop: 12, - fontSize: 16, - color: "#888", - textAlign: "center", - maxWidth: 280, - }, - permissionButton: { - marginTop: 24, - backgroundColor: "#007AFF", - paddingHorizontal: 32, - paddingVertical: 14, - borderRadius: 12, - }, - permissionButtonText: { - color: "#fff", - fontSize: 18, - fontWeight: "600", - }, - errorText: { - marginTop: 16, - fontSize: 18, - color: "#FF6B6B", - textAlign: "center", - }, - overlay: { - ...StyleSheet.absoluteFillObject, - }, - overlayTop: { - flex: 1, - backgroundColor: "rgba(0,0,0,0.6)", - }, - overlayMiddle: { - flexDirection: "row", - height: SCAN_BOX_HEIGHT, - }, - overlaySide: { - flex: 1, - backgroundColor: "rgba(0,0,0,0.6)", - }, - scanBox: { - width: SCAN_BOX_WIDTH, - height: SCAN_BOX_HEIGHT, - borderWidth: 2, - borderColor: "rgba(255,255,255,0.3)", - borderRadius: 12, - }, - overlayBottom: { - flex: 1, - backgroundColor: "rgba(0,0,0,0.6)", - }, - corner: { - position: "absolute", - width: 24, - height: 24, - borderColor: "#007AFF", - }, - cornerTopLeft: { - top: -2, - left: -2, - borderTopWidth: 4, - borderLeftWidth: 4, - borderTopLeftRadius: 12, - }, - cornerTopRight: { - top: -2, - right: -2, - borderTopWidth: 4, - borderRightWidth: 4, - borderTopRightRadius: 12, - }, - cornerBottomLeft: { - bottom: -2, - left: -2, - borderBottomWidth: 4, - borderLeftWidth: 4, - borderBottomLeftRadius: 12, - }, - cornerBottomRight: { - bottom: -2, - right: -2, - borderBottomWidth: 4, - borderRightWidth: 4, - borderBottomRightRadius: 12, - }, - instructionContainer: { - position: "absolute", - top: 60, - left: 0, - right: 0, - alignItems: "center", - }, - instructionText: { - color: "#fff", - fontSize: 14, - backgroundColor: "rgba(0,0,0,0.5)", - paddingHorizontal: 16, - paddingVertical: 8, - borderRadius: 20, - }, - devModeText: { - marginTop: 8, - color: "#FFD700", - fontSize: 12, - backgroundColor: "rgba(0,0,0,0.5)", - paddingHorizontal: 12, - paddingVertical: 4, - borderRadius: 12, - }, - resultContainer: { - position: "absolute", - bottom: 140, - left: 16, - right: 16, - }, - resultCard: { - flexDirection: "row", - backgroundColor: "rgba(0,0,0,0.9)", - borderRadius: 12, - padding: 12, - alignItems: "center", - }, - resultImage: { - width: 50, - height: 70, - borderRadius: 4, - }, - resultInfo: { - flex: 1, - marginLeft: 12, - }, - resultName: { - color: "#fff", - fontSize: 16, - fontWeight: "600", - }, - resultSet: { - color: "#888", - fontSize: 12, - marginTop: 2, - }, - resultConfidence: { - color: "#4CD964", - fontSize: 12, - marginTop: 4, - }, - addButton: { - flexDirection: "row", - alignItems: "center", - backgroundColor: "#4CD964", - paddingHorizontal: 16, - paddingVertical: 10, - borderRadius: 8, - gap: 6, - }, - addButtonDisabled: { - opacity: 0.5, - }, - addButtonText: { - color: "#fff", - fontSize: 14, - fontWeight: "600", - }, - errorContainer: { - position: "absolute", - bottom: 140, - left: 16, - right: 16, - backgroundColor: "rgba(255,107,107,0.95)", - borderRadius: 12, - padding: 16, - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - }, - errorMessage: { - color: "#fff", - fontSize: 14, - flex: 1, - }, - errorDismiss: { - color: "#fff", - fontSize: 14, - fontWeight: "600", - marginLeft: 12, - }, - controls: { - position: "absolute", - bottom: 40, - left: 0, - right: 0, - flexDirection: "row", - justifyContent: "space-around", - alignItems: "center", - paddingHorizontal: 40, - }, - controlButton: { - width: 50, - height: 50, - justifyContent: "center", - alignItems: "center", - }, - captureButton: { - width: 72, - height: 72, - borderRadius: 36, - backgroundColor: "#fff", - justifyContent: "center", - alignItems: "center", - borderWidth: 4, - borderColor: "rgba(255,255,255,0.3)", - }, - captureButtonDisabled: { - opacity: 0.5, - }, - captureButtonInner: { - width: 56, - height: 56, - borderRadius: 28, - backgroundColor: "#fff", - }, -}); diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx deleted file mode 100644 index 5fa9c83..0000000 --- a/app/(tabs)/settings.tsx +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Settings screen for Scry app. - */ - -import React, { useState } from "react"; -import { - StyleSheet, - View, - Text, - ScrollView, - Pressable, - Switch, - ActivityIndicator, - Alert, -} from "react-native"; -import { FontAwesome } from "@expo/vector-icons"; -import { useQuery } from "convex/react"; -import { api } from "../../convex/_generated/api"; -import { useSync } from "@/lib/hooks/useSync"; -import { useHashCache } from "@/lib/context"; - -interface SettingRowProps { - icon: React.ComponentProps["name"]; - title: string; - subtitle?: string; - onPress?: () => void; - rightElement?: React.ReactNode; - destructive?: boolean; -} - -function SettingRow({ - icon, - title, - subtitle, - onPress, - rightElement, - destructive, -}: SettingRowProps) { - return ( - [ - styles.settingRow, - pressed && onPress && styles.settingRowPressed, - ]} - onPress={onPress} - disabled={!onPress} - > - - - - - - {title} - - {subtitle && {subtitle}} - - {rightElement || (onPress && )} - - ); -} - -function SettingSection({ title, children }: { title: string; children: React.ReactNode }) { - return ( - - {title} - {children} - - ); -} - -export default function SettingsScreen() { - const [cardDetectionEnabled, setCardDetectionEnabled] = useState(true); - const [rotationMatchingEnabled, setRotationMatchingEnabled] = useState(true); - - // Get hash count from context - const { cardHashes, hashesLoaded } = useHashCache(); - - // Sync hook for cache management - const { isInitialized, isSyncing, lastSync, localCardCount, error: syncError, sync, clearCache } = - useSync(); - - // Get total card count from Convex - const cardCount = useQuery(api.cards.count); - - const formatLastSync = (timestamp: number) => { - if (!timestamp) return "Never"; - const date = new Date(timestamp); - return date.toLocaleDateString() + " " + date.toLocaleTimeString(); - }; - - const handleClearCache = () => { - Alert.alert( - "Clear Local Cache", - "This will remove all downloaded card data. You'll need to sync again to scan cards.", - [ - { text: "Cancel", style: "cancel" }, - { - text: "Clear", - style: "destructive", - onPress: async () => { - await clearCache(); - Alert.alert("Cache Cleared", "Local card data has been removed."); - }, - }, - ] - ); - }; - - const handleManualSync = async () => { - await sync(); - Alert.alert("Sync Complete", `${localCardCount} cards now available for scanning.`); - }; - - return ( - - {/* Database section */} - - : undefined} - /> - : undefined} - /> - {syncError && ( - - )} - - - {/* Recognition section */} - - - } - /> - - } - /> - - - {/* Collection section */} - - - { - // TODO: Implement export - }} - /> - - - {/* About section */} - - - { - // TODO: Open source URL - }} - /> - - - {/* Danger zone */} - - { - Alert.alert( - "Clear Collection", - "This will remove all cards from your collection. This cannot be undone.", - [ - { text: "Cancel", style: "cancel" }, - { text: "Clear", style: "destructive", onPress: () => {} }, - ] - ); - }} - destructive - /> - - - - {/* Footer */} - - Scry • Card scanner for Magic: The Gathering - Card data © Wizards of the Coast - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#1a1a1a", - }, - content: { - paddingBottom: 40, - }, - section: { - marginTop: 24, - }, - sectionTitle: { - color: "#888", - fontSize: 13, - fontWeight: "600", - textTransform: "uppercase", - letterSpacing: 0.5, - marginLeft: 16, - marginBottom: 8, - }, - sectionContent: { - backgroundColor: "#2a2a2a", - borderRadius: 12, - marginHorizontal: 16, - overflow: "hidden", - }, - settingRow: { - flexDirection: "row", - alignItems: "center", - padding: 14, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: "#3a3a3a", - }, - settingRowPressed: { - backgroundColor: "#333", - }, - settingIcon: { - width: 32, - height: 32, - borderRadius: 8, - backgroundColor: "rgba(0,122,255,0.1)", - justifyContent: "center", - alignItems: "center", - marginRight: 12, - }, - settingIconDestructive: { - backgroundColor: "rgba(255,107,107,0.1)", - }, - settingContent: { - flex: 1, - }, - settingTitle: { - color: "#fff", - fontSize: 16, - }, - settingTitleDestructive: { - color: "#FF6B6B", - }, - settingSubtitle: { - color: "#888", - fontSize: 13, - marginTop: 2, - }, - footer: { - marginTop: 40, - alignItems: "center", - paddingHorizontal: 16, - }, - footerText: { - color: "#666", - fontSize: 14, - }, - footerSubtext: { - color: "#444", - fontSize: 12, - marginTop: 4, - }, -}); diff --git a/app/+html.tsx b/app/+html.tsx deleted file mode 100644 index cb31090..0000000 --- a/app/+html.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ScrollViewStyleReset } from 'expo-router/html'; - -// This file is web-only and used to configure the root HTML for every -// web page during static rendering. -// The contents of this function only run in Node.js environments and -// do not have access to the DOM or browser APIs. -export default function Root({ children }: { children: React.ReactNode }) { - return ( - - - - - - - {/* - Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. - However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. - */} - - - {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} -