diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..d76a042
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,3 @@
+NEXT_PUBLIC_SUPABASE_URL=
+NEXT_PUBLIC_SUPABASE_ANON_KEY=
+SUPABASE_SERVICE_ROLE_KEY=
diff --git a/README.md b/README.md
index 0e1c824..ffd7321 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,46 @@
# VaultLens
-Collector intelligence app for identifying, valuing, and tracking trading cards and collectibles.
+VaultLens is a collector intelligence app for identifying, valuing, and tracking trading cards and collectibles.
+The goal is to help collectors understand:
+- what they own
+- what it may be worth
+- why it may be worth that amount
+- whether it may be worth grading, selling, holding, or researching further
+## MVP Focus
+The first version focuses on sports cards.
+Core features:
+- upload/scan card images
+- identify card metadata
+- estimate raw market value
+- compare graded value ranges
+- save cards to a personal collection
+- provide confidence scoring and next-step recommendations
+## Product Principle
+VaultLens should not simply guess a price.
+It should explain the valuation:
+- comparable sales
+- condition assumptions
+- grading context
+- population/scarcity context
+- confidence level
+## Initial Tech Stack
+- Next.js
+- TypeScript
+- Tailwind CSS
+- Supabase
+- Vercel
+## Status
+Early MVP.
+
+## Local Setup
+1. Install dependencies:
+ ```bash
+ npm install
+ ```
+2. Copy environment file:
+ ```bash
+ cp .env.example .env.local
+ ```
+3. Start development server:
+ ```bash
+ npm run dev
+ ```
diff --git a/docs/DATA_PROVIDERS.md b/docs/DATA_PROVIDERS.md
new file mode 100644
index 0000000..70c4729
--- /dev/null
+++ b/docs/DATA_PROVIDERS.md
@@ -0,0 +1,18 @@
+# DATA PROVIDERS
+
+VaultLens uses a provider architecture so data sources can be swapped without rewriting product workflows.
+
+## Provider Interfaces
+- `CardIdentificationProvider`
+- `MarketDataProvider`
+- `GradingDataProvider`
+
+## Why
+- Marketplace APIs and grading APIs have limits and access constraints.
+- Mock providers unblock UX and product iteration.
+- Real providers can be implemented behind the same interfaces later.
+
+## Planned Real Integrations
+- eBay discovery/detail (where available)
+- PSA cert/card context
+- Population context providers (CGC/PSA/BGS as available)
diff --git a/docs/MVP_SCOPE.md b/docs/MVP_SCOPE.md
new file mode 100644
index 0000000..83d473c
--- /dev/null
+++ b/docs/MVP_SCOPE.md
@@ -0,0 +1,16 @@
+# MVP SCOPE
+
+## In Scope
+1. User auth-ready structure (Supabase-ready).
+2. Scan/upload flow for front/back card images.
+3. Mock card identification result.
+4. Card detail page with metadata.
+5. Market value range (mock).
+6. Grading insights (mock).
+7. Confidence score and recommendation.
+8. Collection tracker with local mock persistence.
+
+## Out of Scope (for now)
+- Production OCR/computer vision.
+- Live sold-comparable integrations.
+- Native mobile apps.
diff --git a/docs/PRODUCT_BRIEF.md b/docs/PRODUCT_BRIEF.md
new file mode 100644
index 0000000..ef23236
--- /dev/null
+++ b/docs/PRODUCT_BRIEF.md
@@ -0,0 +1,12 @@
+# PRODUCT BRIEF
+
+## Vision
+VaultLens is a collector intelligence platform that helps hobbyists understand what they own, what it may be worth, and what action makes sense next.
+
+## MVP Focus
+- Sports cards only.
+- Web app / PWA style UX first.
+- Mock provider architecture for identification, market, and grading data.
+
+## Differentiator
+VaultLens does not only show a number. It explains value range, confidence, and recommended next action.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
new file mode 100644
index 0000000..01d2cf4
--- /dev/null
+++ b/docs/ROADMAP.md
@@ -0,0 +1,27 @@
+# ROADMAP
+
+## Phase 1 (Current)
+- Next.js scaffold
+- Landing, scan, card detail, collection pages
+- Mock provider interfaces and implementations
+
+## Phase 2
+- Confidence model breakdown and explainability
+- "Why this value?" module
+- Worth grading decision helper
+
+## Phase 3
+- Collection tracking expansion (quantity, grade, notes, value totals)
+- Supabase persistence and auth
+
+## Planned GitHub Issues
+1. Scaffold Next.js app and landing page
+2. Build scan/upload flow
+3. Add mock card identification provider
+4. Add card result page
+5. Add market value and grading insight UI
+6. Add confidence score logic
+7. Add collection tracking
+8. Add Supabase schema
+9. Add real marketplace provider interface
+10. Add grading/population provider interface
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644
index 0000000..84ab714
--- /dev/null
+++ b/next-env.d.ts
@@ -0,0 +1,4 @@
+///
+///
+
+// NOTE: This file should not be edited
diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..b44fec7
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ experimental: {
+ typedRoutes: true
+ }
+};
+
+export default nextConfig;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7b8d090
--- /dev/null
+++ b/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "vaultlens",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "14.2.33",
+ "react": "18.3.1",
+ "react-dom": "18.3.1"
+ },
+ "devDependencies": {
+ "@types/node": "20.17.30",
+ "@types/react": "18.3.23",
+ "@types/react-dom": "18.3.7",
+ "autoprefixer": "10.4.21",
+ "eslint": "8.57.1",
+ "eslint-config-next": "14.2.33",
+ "postcss": "8.5.3",
+ "tailwindcss": "3.4.17",
+ "typescript": "5.8.3"
+ }
+}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..ba80730
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {}
+ }
+};
diff --git a/src/app/api/cards/route.ts b/src/app/api/cards/route.ts
new file mode 100644
index 0000000..96d72a3
--- /dev/null
+++ b/src/app/api/cards/route.ts
@@ -0,0 +1,6 @@
+import { NextResponse } from "next/server";
+import { mockCollection } from "@/lib/mock-collection";
+
+export async function GET() {
+ return NextResponse.json(mockCollection);
+}
diff --git a/src/app/api/market/route.ts b/src/app/api/market/route.ts
new file mode 100644
index 0000000..97a2f83
--- /dev/null
+++ b/src/app/api/market/route.ts
@@ -0,0 +1,9 @@
+import { NextResponse } from "next/server";
+import { marketDataProvider } from "@/lib/market-data";
+import { cardIdentificationProvider } from "@/lib/card-identification";
+
+export async function GET() {
+ const card = await cardIdentificationProvider.identify("mock-front");
+ const market = await marketDataProvider.estimate(card);
+ return NextResponse.json(market);
+}
diff --git a/src/app/api/scan/route.ts b/src/app/api/scan/route.ts
new file mode 100644
index 0000000..7721b22
--- /dev/null
+++ b/src/app/api/scan/route.ts
@@ -0,0 +1,7 @@
+import { NextResponse } from "next/server";
+import { cardIdentificationProvider } from "@/lib/card-identification";
+
+export async function POST() {
+ const card = await cardIdentificationProvider.identify("mock-front");
+ return NextResponse.json(card);
+}
diff --git a/src/app/card/[id]/page.tsx b/src/app/card/[id]/page.tsx
new file mode 100644
index 0000000..a958ed5
--- /dev/null
+++ b/src/app/card/[id]/page.tsx
@@ -0,0 +1,44 @@
+import { Nav } from "@/components/nav";
+import { confidenceBucket } from "@/lib/confidence";
+import { mockCollection } from "@/lib/mock-collection";
+
+export default function CardDetailPage({ params }: { params: { id: string } }) {
+ const result = mockCollection.find((item) => item.card.id === params.id) ?? mockCollection[0];
+
+ return (
+
+
+
+ {result.card.name}
+
+ {result.card.year} · {result.card.setName} · #{result.card.cardNumber} · {result.card.player} · {result.card.sport}
+
+
+
+
+ Market value estimate
+ ${result.market.rawValueLow.toLocaleString()} – ${result.market.rawValueHigh.toLocaleString()}
+ Source: {result.market.comparableSalesSource}
+
+
+
+ Confidence score
+ {result.card.confidenceScore}/100 ({confidenceBucket(result.card.confidenceScore)})
+ Recommendation: {result.recommendation}
+
+
+
+
+ Grading context
+
+ {result.grading.map((grade) => (
+ -
+ {grade.company}: ${grade.valueLow.toLocaleString()} – ${grade.valueHigh.toLocaleString()}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/app/collection/page.tsx b/src/app/collection/page.tsx
new file mode 100644
index 0000000..62c1237
--- /dev/null
+++ b/src/app/collection/page.tsx
@@ -0,0 +1,28 @@
+import Link from "next/link";
+import { Nav } from "@/components/nav";
+import { mockCollection } from "@/lib/mock-collection";
+
+export default function CollectionPage() {
+ return (
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..f418125
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,11 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ color-scheme: light;
+}
+
+body {
+ @apply bg-slate-50 text-slate-900;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..e0e8e56
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,17 @@
+import type { Metadata } from "next";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "VaultLens",
+ description: "Collector intelligence for sports cards"
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 0000000..4dd9901
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,24 @@
+import Link from "next/link";
+import { Nav } from "@/components/nav";
+
+export default function HomePage() {
+ return (
+
+
+
+ VaultLens
+
+ Collector intelligence platform for understanding what your cards are worth, why, and what to do next.
+
+
+
+ Start a scan
+
+
+ View collection
+
+
+
+
+ );
+}
diff --git a/src/app/scan/page.tsx b/src/app/scan/page.tsx
new file mode 100644
index 0000000..9a05c21
--- /dev/null
+++ b/src/app/scan/page.tsx
@@ -0,0 +1,27 @@
+import Link from "next/link";
+import { Nav } from "@/components/nav";
+
+export default function ScanPage() {
+ return (
+
+
+
+ Scan / Upload Card
+ Upload front image and optional back image. MVP uses a mock provider.
+
+
+
+ );
+}
diff --git a/src/components/nav.tsx b/src/components/nav.tsx
new file mode 100644
index 0000000..61de1b7
--- /dev/null
+++ b/src/components/nav.tsx
@@ -0,0 +1,19 @@
+import Link from "next/link";
+
+const links = [
+ { href: "/", label: "Home" },
+ { href: "/scan", label: "Scan" },
+ { href: "/collection", label: "Collection" }
+];
+
+export function Nav() {
+ return (
+
+ );
+}
diff --git a/src/lib/card-identification.ts b/src/lib/card-identification.ts
new file mode 100644
index 0000000..8d6d708
--- /dev/null
+++ b/src/lib/card-identification.ts
@@ -0,0 +1,3 @@
+import { MockCardIdentificationProvider } from "./providers/mock-provider";
+
+export const cardIdentificationProvider = new MockCardIdentificationProvider();
diff --git a/src/lib/confidence.ts b/src/lib/confidence.ts
new file mode 100644
index 0000000..1e8875a
--- /dev/null
+++ b/src/lib/confidence.ts
@@ -0,0 +1,5 @@
+export function confidenceBucket(score: number): "low" | "medium" | "high" {
+ if (score >= 80) return "high";
+ if (score >= 60) return "medium";
+ return "low";
+}
diff --git a/src/lib/grading-data.ts b/src/lib/grading-data.ts
new file mode 100644
index 0000000..09ace73
--- /dev/null
+++ b/src/lib/grading-data.ts
@@ -0,0 +1,3 @@
+import { MockGradingDataProvider } from "./providers/mock-provider";
+
+export const gradingDataProvider = new MockGradingDataProvider();
diff --git a/src/lib/market-data.ts b/src/lib/market-data.ts
new file mode 100644
index 0000000..b02f5be
--- /dev/null
+++ b/src/lib/market-data.ts
@@ -0,0 +1,3 @@
+import { MockMarketDataProvider } from "./providers/mock-provider";
+
+export const marketDataProvider = new MockMarketDataProvider();
diff --git a/src/lib/mock-collection.ts b/src/lib/mock-collection.ts
new file mode 100644
index 0000000..307dd7e
--- /dev/null
+++ b/src/lib/mock-collection.ts
@@ -0,0 +1,31 @@
+import type { CollectionCard } from "@/types/card";
+
+export const mockCollection: CollectionCard[] = [
+ {
+ card: {
+ id: "card-1986-jordan-fleer-57",
+ name: "1986 Fleer Michael Jordan Rookie",
+ player: "Michael Jordan",
+ year: 1986,
+ setName: "Fleer Basketball",
+ cardNumber: "57",
+ sport: "Basketball",
+ confidenceScore: 82
+ },
+ market: {
+ rawValueLow: 2200,
+ rawValueHigh: 3400,
+ comparableSalesSource: "Mock comparable sales feed",
+ confidenceLevel: "medium"
+ },
+ grading: [
+ { company: "PSA", valueLow: 6500, valueHigh: 12000 },
+ { company: "CGC", valueLow: 5200, valueHigh: 9800 }
+ ],
+ recommendation: "grade",
+ ownedQuantity: 1,
+ conditionEstimate: "Near Mint",
+ gradedStatus: "ungraded",
+ notes: "Centering looks strong."
+ }
+];
diff --git a/src/lib/providers/interfaces.ts b/src/lib/providers/interfaces.ts
new file mode 100644
index 0000000..c55829e
--- /dev/null
+++ b/src/lib/providers/interfaces.ts
@@ -0,0 +1,13 @@
+import type { CardIdentificationResult, GradingEstimate, MarketEstimate } from "@/types/card";
+
+export interface CardIdentificationProvider {
+ identify(frontImageUrl: string, backImageUrl?: string): Promise;
+}
+
+export interface MarketDataProvider {
+ estimate(card: CardIdentificationResult): Promise;
+}
+
+export interface GradingDataProvider {
+ estimate(card: CardIdentificationResult): Promise;
+}
diff --git a/src/lib/providers/mock-provider.ts b/src/lib/providers/mock-provider.ts
new file mode 100644
index 0000000..0021049
--- /dev/null
+++ b/src/lib/providers/mock-provider.ts
@@ -0,0 +1,41 @@
+import type { CardIdentificationResult, GradingEstimate, MarketEstimate } from "@/types/card";
+import type { CardIdentificationProvider, GradingDataProvider, MarketDataProvider } from "./interfaces";
+
+const mockCard: CardIdentificationResult = {
+ id: "card-1986-jordan-fleer-57",
+ name: "1986 Fleer Michael Jordan Rookie",
+ player: "Michael Jordan",
+ year: 1986,
+ setName: "Fleer Basketball",
+ cardNumber: "57",
+ sport: "Basketball",
+ confidenceScore: 82
+};
+
+export class MockCardIdentificationProvider implements CardIdentificationProvider {
+ async identify(): Promise {
+ return Promise.resolve(mockCard);
+ }
+}
+
+export class MockMarketDataProvider implements MarketDataProvider {
+ async estimate(): Promise {
+ return Promise.resolve({
+ rawValueLow: 2200,
+ rawValueHigh: 3400,
+ comparableSalesSource: "Mock comparable sales feed",
+ confidenceLevel: "medium"
+ });
+ }
+}
+
+export class MockGradingDataProvider implements GradingDataProvider {
+ async estimate(): Promise {
+ return Promise.resolve([
+ { company: "PSA", valueLow: 6500, valueHigh: 12000 },
+ { company: "CGC", valueLow: 5200, valueHigh: 9800 },
+ { company: "SGC", valueLow: 4700, valueHigh: 8700 },
+ { company: "BGS", valueLow: 6100, valueHigh: 11100 }
+ ]);
+ }
+}
diff --git a/src/types/card.ts b/src/types/card.ts
new file mode 100644
index 0000000..52d187c
--- /dev/null
+++ b/src/types/card.ts
@@ -0,0 +1,39 @@
+export type Recommendation = "hold" | "sell" | "grade" | "research";
+
+export interface CardIdentificationResult {
+ id: string;
+ name: string;
+ player: string;
+ year: number;
+ setName: string;
+ cardNumber: string;
+ sport: string;
+ confidenceScore: number;
+}
+
+export interface MarketEstimate {
+ rawValueLow: number;
+ rawValueHigh: number;
+ comparableSalesSource: string;
+ confidenceLevel: "low" | "medium" | "high";
+}
+
+export interface GradingEstimate {
+ company: "PSA" | "CGC" | "SGC" | "BGS";
+ valueLow: number;
+ valueHigh: number;
+}
+
+export interface CardInsight {
+ card: CardIdentificationResult;
+ market: MarketEstimate;
+ grading: GradingEstimate[];
+ recommendation: Recommendation;
+}
+
+export interface CollectionCard extends CardInsight {
+ ownedQuantity: number;
+ conditionEstimate: string;
+ gradedStatus: "graded" | "ungraded";
+ notes?: string;
+}
diff --git a/supabase/schema.sql b/supabase/schema.sql
new file mode 100644
index 0000000..ec531de
--- /dev/null
+++ b/supabase/schema.sql
@@ -0,0 +1,67 @@
+create table if not exists users (
+ id uuid primary key,
+ email text unique not null,
+ created_at timestamptz default now()
+);
+
+create table if not exists cards (
+ id uuid primary key default gen_random_uuid(),
+ user_id uuid references users(id),
+ name text not null,
+ player text,
+ year int,
+ set_name text,
+ card_number text,
+ sport text,
+ manufacturer text,
+ variant text,
+ image_url text,
+ condition_estimate text,
+ raw_value_low numeric,
+ raw_value_high numeric,
+ graded_value_low numeric,
+ graded_value_high numeric,
+ confidence_score numeric,
+ recommendation text,
+ created_at timestamptz default now(),
+ updated_at timestamptz default now()
+);
+
+create table if not exists collections (
+ id uuid primary key default gen_random_uuid(),
+ user_id uuid references users(id),
+ name text default 'Default Collection',
+ created_at timestamptz default now()
+);
+
+create table if not exists card_scans (
+ id uuid primary key default gen_random_uuid(),
+ user_id uuid references users(id),
+ card_id uuid references cards(id),
+ front_image_url text,
+ back_image_url text,
+ scan_status text,
+ created_at timestamptz default now()
+);
+
+create table if not exists market_estimates (
+ id uuid primary key default gen_random_uuid(),
+ card_id uuid references cards(id),
+ provider text,
+ value_low numeric,
+ value_high numeric,
+ confidence_level text,
+ metadata jsonb,
+ created_at timestamptz default now()
+);
+
+create table if not exists grading_estimates (
+ id uuid primary key default gen_random_uuid(),
+ card_id uuid references cards(id),
+ grading_company text,
+ grade text,
+ value_low numeric,
+ value_high numeric,
+ metadata jsonb,
+ created_at timestamptz default now()
+);
diff --git a/tailwind.config.ts b/tailwind.config.ts
new file mode 100644
index 0000000..8337894
--- /dev/null
+++ b/tailwind.config.ts
@@ -0,0 +1,11 @@
+import type { Config } from "tailwindcss";
+
+const config: Config = {
+ content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
+ theme: {
+ extend: {}
+ },
+ plugins: []
+};
+
+export default config;
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..caa4325
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}