# Project export: alt+cart

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Seamless, sustainable shopping for the visually impaired
- Devpost: https://devpost.com/software/alt-cart
- GitHub: https://github.com/Supernova-45/Alt-Cart
- Demo: https://alt-cart.vercel.app/
- Video: https://www.youtube.com/embed/V2v3RWqgHsw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Alexandra Kim (79 commits), Emma Li (4 commits)

## Devpost submission (written by the team)

### Inspiration

330 million people globally are visually impaired. 81 percent of shoppers with disabilities experience critical issues while online shopping. Two-thirds of shopping transactions initiated by visually impaired users are abandoned due to websites being inaccessible. Yet today, there exist no online shopping-centric platforms designed for the visually impaired. Screen readers can read the DOM, but e-commerce websites aren't documents. Due to nonstandard layouts and scattered images, existing toolscan’t reliably answer the questions that matter: What does this item actually look like? Will it fit me? Would I regret this purchase? Is there a greener choice?

### What it does

alt+cart is a Chrome extension + web app that makes shopping readable again: 1) Hold Alt + hover to hear what’s on the page On any product page, hold Alt and hover over an image (or text region) to hear a spoken description of shopping-relevant details. 1) Search for any item and get an accessible product info page: Name + price + popularity Key specs (materials, dimensions, compatibility, care) What people complain about in reviews Sustainability signals (materials, certifications, manufacturing, badges) 2) Select 2-4 items for a conversational side-by-side comparison: fit/sizing conflicts (“true to size” vs “runs small”) quality/durability themes return-risk drivers All of this can be navigated with just keyboard. We include TTS controls, captions and sentence highlighting, and user configuration options for font size, dyslexia-friendly font, reduced motion, and high contrast.

### How we built it

Product extraction: Browserbase gives us stable, remote Chrome sessions that work inside serverless deployment. Stagehand drives navigation, waits for dynamic content, and runs DOM queries.certifications. Bright Data powers catalog search for known domains (Amazon, Walmart, eBay, Etsy, Lowe's, Target, Macy's, Home Depot). We try Bright Data first for speed and reliability. Search results are then sortable by relevance, price, or rating. Pipeline: Backend: Extracted reviews feed into fit analysis (keyword matching for "runs small", "true to size") and thematic extraction (quality, value, style, sizing). Return risk is computed from rating distribution and review complaints. Sustainability scores come from materials, certifications, origin, and badges. Image descriptions are generated for the main product image. Website: Node.js, Express, TypeScript, Zod, React, Web Speech API (TTS). Deployed on Vercel. Chrome extension: Chrome Manifest v3, content script for Alt+hover image descriptions.

### Challenges we ran into

Fragmented site structures: Amazon, Walmart, and Macy's each structure product pages differently. We built five domain-specific extractors with fallback selectors (e.g. #productTitle vs meta[property="og:title"] vs h1) to handle variation. Rate limits and latency: Bright Data and Browserbase can take over a minute to return a response, which posed challenges we tried to address with a fallback system. Reviews are messy and contradictory: Efficiently extracting meaningful information from free-form review text required keyword sets and severity scoring.

### What's next

More realistic AI text-to-speech options A hands-free conversational voice agent for direct interaction while shopping Improving extraction robustness across a diverse set of e-commerce sites using LLMs A more personalized experience for each user, describing their preferred fit, items they've bought in the past, and their sustainability preferences.

## README (from the GitHub repository)

# alt+cart : Seamless, sustainable shopping for the visually impaired

## Inspiration

330 million people globally are visually impaired. 81 percent of shoppers with disabilities experience critical issues while online shopping. Two-thirds of shopping transactions initiated by visually impaired users are abandoned due to websites being inaccessible.

Yet today, there exist *no online shopping-centric platforms* designed for the visually impaired. Screen readers can read the DOM, but e-commerce websites aren't documents. Due to nonstandard layouts and scattered images, existing toolscan’t reliably answer the questions that matter: 
- What does this item actually look like? 
- Will it fit me?
- Would I regret this purchase?
- Is there a greener choice?

## What it does

**alt+cart** is a Chrome extension + web app that makes shopping readable again:

1) Hold Alt + hover to hear what’s on the page
- On any product page, hold Alt and hover over an image (or text region) to hear a spoken description of shopping-relevant details.

1) Search for any item and get an accessible product info page:
  - Name + price + popularity
  - Key specs (materials, dimensions, compatibility, care)
  - What people complain about in reviews
  - Sustainability signals (materials, certifications, manufacturing, badges)

2) Select 2-4 items for a conversational side-by-side comparison: 
  - fit/sizing conflicts (“true to size” vs “runs small”)
  - quality/durability themes
  - return-risk drivers

All of this can be navigated with just keyboard. We include TTS controls, captions and sentence highlighting, and user configuration options for font size, dyslexia-friendly font, reduced motion, and high contrast.

## How we built it

**Product extraction:** 
- **Browserbase** gives us stable, remote Chrome sessions that work inside serverless deployment.
- **Stagehand** drives navigation, waits for dynamic content, and runs DOM queries.certifications.
- **Bright Data** powers catalog search for known domains (Amazon, Walmart, eBay, Etsy, Lowe's, Target, Macy's, Home Depot). We try Bright Data first for speed and reliability. Search results are then sortable by relevance, price, or rating.

**Pipeline:**
- **Backend:** Extracted reviews feed into fit analysis (keyword matching for "runs small", "true to size") and thematic extraction (quality, value, style, sizing). Return risk is computed from rating distribution and review complaints. Sustainability scores come from materials, certifications, origin, and badges. Image descriptions are generated for the main product image.
- **Website:** Node.js, Express, TypeScript, Zod, React, Web Speech API (TTS). Deployed on Vercel.
- **Chrome extension:** Chrome Manifest v3, content script for Alt+hover image descriptions.

## Challenges we ran into

- **Fragmented site structures:** Amazon, Walmart, and Macy's each structure product pages differently. We built five domain-specific extractors with fallback selectors (e.g. `#productTitle` vs `meta[property="og:title"]` vs `h1`) to handle variation.
- **Rate limits and latency:** Bright Data and Browserbase can take over a minute to return a response, which posed challenges we tried to address with a fallback system.
- **Reviews are messy and contradictory:** Efficiently extracting meaningful information from free-form review text required keyword sets and severity scoring.

## What's next

- More realistic AI text-to-speech options
- A hands-free conversational voice agent for direct interaction while shopping
- Improving extraction robustness across a diverse set of e-commerce sites using LLMs
- A more personalized experience for each user, describing their preferred fit, items they've bought in the past, and their sustainability preferences.


## Detected evidence (automated analysis)

Indexed codebase: 112 recognized source files, 20629 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 129)

```
.DS_Store
.gitignore
backend/.env.example
backend/.gitignore
backend/package.json
backend/README.md
backend/src/config/env.ts
backend/src/extractors/amazonExtractor.ts
backend/src/extractors/amazonSearchExtractor.ts
backend/src/extractors/catalogHtmlParsers/amazonSearchHtmlParser.ts
backend/src/extractors/catalogHtmlParsers/walmartSearchHtmlParser.ts
backend/src/extractors/ebayExtractor.ts
backend/src/extractors/genericSearchExtractor.ts
backend/src/extractors/macysExtractor.ts
backend/src/extractors/targetExtractor.ts
backend/src/extractors/walmartExtractor.ts
backend/src/extractors/walmartSearchExtractor.ts
backend/src/middleware/cors.ts
backend/src/middleware/errorHandler.ts
backend/src/models/productModel.ts
backend/src/routes/health.ts
backend/src/routes/products.ts
backend/src/routes/search.ts
backend/src/server.ts
backend/src/services/brightDataCatalogService.ts
backend/src/services/brightDataService.ts
backend/src/services/extractionService.ts
backend/src/services/searchExtractionService.ts
backend/src/services/stagehandService.ts
backend/src/services/transformService.ts
backend/src/types/search.ts
backend/src/utils/logger.ts
backend/src/utils/parseReviewCount.ts
backend/src/utils/returnRiskGenerator.ts
backend/src/utils/searchUrlParser.ts
backend/src/utils/sustainabilityGenerator.ts
backend/src/utils/urlParser.ts
backend/test-stagehand.js
backend/tsconfig.json
backend/vercel.json
extension/content.js
extension/manifest.json
extension/popup.css
extension/popup.html
extension/popup.js
extension/service_worker.js
mvp_spec.md
README.md
web/.env
web/.env.example
web/eslint.config.js
web/index.html
web/package.json
web/public/.DS_Store
web/public/webpages/.DS_Store
web/public/webpages/amazon_adidas.html
web/public/webpages/amazon_adokoo.html
web/public/webpages/amazon_on.html
web/public/webpages/amazon_puma.html
web/public/webpages/amazon_white_sneakers.html
web/public/webpages/walmart_backpack.html
web/public/webpages/walmart_dakimoe.html
web/public/webpages/walmart_eastsport.html
web/public/webpages/walmart_honglong.html
web/public/webpages/walmart_madden.html
web/public/webpages/walmart_ozark.html
web/public/webpages/walmart_reebok.html
web/src/App.tsx
web/src/components/CompareBar.tsx
web/src/components/CompareModeContext.tsx
web/src/components/HelpButton.tsx
web/src/components/HelpModal.tsx
web/src/components/ProductCard.tsx
web/src/components/ProductList.tsx
web/src/components/SectionCard.tsx
web/src/components/Shell.tsx
web/src/components/SkipLink.tsx
web/src/components/StatPill.tsx
web/src/components/ThemeToggle.tsx
web/src/components/TopBar.tsx
web/src/components/TTSCaptions.tsx
web/src/components/TTSControls.tsx
web/src/lib/accessibilityPreferences.ts
web/src/lib/api.ts
web/src/lib/buildComparison.ts
web/src/lib/compare.ts
web/src/lib/demoFallbacks.ts
web/src/lib/fetchSnapshot.ts
web/src/lib/formatReviewCount.ts
web/src/lib/lowVision.ts
web/src/lib/mergePassport.ts
web/src/lib/parseProductSnapshot.ts
web/src/lib/passportCache.ts
web/src/lib/passportToSearchItem.ts
web/src/lib/preferences.ts
web/src/lib/productModel.ts
web/src/lib/returnRiskGenerator.ts
web/src/lib/searchResultsCache.ts
web/src/lib/snapshotRegistry.ts
web/src/lib/sortSearchResults.ts
web/src/lib/sustainabilityGenerator.ts
web/src/lib/theme.ts
web/src/lib/tts.ts
web/src/lib/urlToDemo.ts
web/src/main.tsx
web/src/routes/Compare.tsx
web/src/routes/Extract.tsx
web/src/routes/ExtractSearch.tsx
web/src/routes/Home.tsx
web/src/routes/Landing.tsx
web/src/routes/NotFound.tsx
web/src/routes/Open.tsx
web/src/routes/Passport.tsx
web/src/routes/Preferences.tsx
web/src/routes/SearchReader.tsx
web/src/routes/Unsupported.tsx
web/src/styles/accessibility.css
web/src/styles/base.css
web/src/styles/components.css
web/src/styles/lowVision.css
[9 more files omitted for size]
```

### Dependencies

- backend/package.json: @browserbasehq/stagehand@^1.0.0, @types/cors@^2.8.17, @types/express@^4.17.21, @types/node@^20.10.0, cheerio@^1.2.0, cors@^2.8.5, dotenv@^16.3.0, express@^4.18.2, nodemon@^3.0.2, tsx@^4.7.0, typescript@^5.3.0, zod@^3.22.0
- web/package.json: @eslint/js@^9.13.0, @types/react@^18.3.12, @types/react-dom@^18.3.1, @vitejs/plugin-react@^4.3.3, eslint@^9.13.0, eslint-plugin-react-hooks@^5.0.0, eslint-plugin-react-refresh@^0.4.14, globals@^15.11.0, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^6.28.0, typescript@~5.6.2, typescript-eslint@^8.11.0, vite@^5.4.10

### Recent commits (newest first)

- New readme
- New help message
- Update documentation
- fix padding
- fix width of box
- fix sorting
- fix gap on search card
- Fix comparison redirect
- fix caching
- add loading icon
- cache the search results
- make notable features separate
- Fix backend build
- Made icons larger
- new extension logos
- fix sustainability scoring
- fix review sentiment
- fix help statement
- make css load faster
- Fix rating extraction

## Key source files (fetched from GitHub, selected and truncated for size)

### mvp_spec.md

```markdown
# alt+cart MVP Spec (Vite + React) — Accessible “Product Passport” from Saved Amazon Pages (Condensed)

This is a **coding-agent-ready spec** for a **minimal, polished, accessible** MVP.  
**Important:** A Chrome extension will be added later, but **DO NOT build the extension in this phase**. The MVP is the web app only.

---

## 0) MVP Objective (demo must succeed)

A visually impaired user can:
1. Open the web app.
2. See a simple list of white sneaker products (from local snapshots / curated list).
3. Open a **Product Passport** page that:
   - Is screen-reader-first (headings/landmarks/skip link).
   - Supports **local TTS** (Web Speech API) with controls.
   - Shows **review warnings** + **return-risk** (hardcoded OK).
   - Has **Low Vision Mode** (bigger type + higher contrast).
4. Navigate end-to-end with keyboard.

### Local snapshots you already saved
- `white_sneakers.html` (search results)
- `adidas.html`, `adokoo.html`, `puma.html`, `on.html`
- Plus associated asset folders 

---

## 1) Scope and non-goals

### Scope
- **Vite + React + TypeScript**
- No backend, no live scraping, no Amazon network calls at runtime.
- Parse local HTML snapshots **best effort** (title/price/rating/count).
- Always have **hardcoded fallback** so demo never breaks.

### Non-goals
- Chrome extension (explicitly not in this build)
- Stagehand/Browserbase, vision models, real review mining, auth, DB

---

## 2) Deliverable: Web app routes

Use `react-router-dom`.

- `/` → **Home**
  - Show **4 curated demo products** (adidas/adokoo/puma/on), each with “Open Passport”.
  - Optional: show “Parsed results (best effort)” from `white_sneakers.html` (read-only list).

- `/passport/:id` → **Passport**
  - Load snapshot HTML for that product ID
  - Parse a few fields best effort
  - Merge into fallback passport data
  - Render a clean, accessible “Passport” view + TTS controls

- `*` → NotFound

---

## 3) Project structure (guidance)

```
public/
  snapshots/
    white_sneakers.html
    adidas.html
    adokoo.html
    puma.html
    on.html
  snapshots_assets/  (copy saved asset folders here to preserve relative paths)

src/
  routes/ Home.tsx, Passport.tsx, NotFound.tsx
  components/ Shell, SkipLink, TopBar, ProductList, ProductCard,
              SectionCard, StatPill, Toggle, TTSControls
  lib/ snapshotRegistry, fetchSnapshot, parseProductSnapshot, parseSearchSnapshot (optional),
       productModel, demoFallbacks, mergePassport, tts
  styles/ tokens.css, base.css, components.css, lowVision.css
```

---

## 4) Snapshot registry (authoritative mapping)

`src/lib/snapshotRegistry.ts`

```ts
export type SnapshotId = "search" | "adidas" | "adokoo" | "puma" | "on";

export const SNAPSHOTS: Record<SnapshotId, { label: string; path: string }> = {
  search: { label: "White sneakers search", path: "/snapshots/white_sneakers.html" },
  adidas: { label: "adidas VL Court 3.0", path: "/snapshots/adidas.html" },
  adokoo: { label: "Adokoo Fashion Sneakers", path: "/snapshots/a
[truncated — 6375 more characters]
```

### web/package.json

```
{
  "name": "altcart",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.28.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.13.0",
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react": "^4.3.3",
    "eslint": "^9.13.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.14",
    "globals": "^15.11.0",
    "typescript": "~5.6.2",
    "typescript-eslint": "^8.11.0",
    "vite": "^5.4.10"
  }
}

```

### backend/package.json

```
{
  "name": "altcart-backend",
  "version": "1.0.0",
  "description": "Backend API for alt+cart product extraction",
  "main": "dist/server.js",
  "scripts": {
    "dev": "nodemon --exec tsx src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js",
    "type-check": "tsc --noEmit"
  },
  "keywords": [
    "accessibility",
    "e-commerce",
    "product-extraction"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@browserbasehq/stagehand": "^1.0.0",
    "cheerio": "^1.2.0",
    "cors": "^2.8.5",
    "dotenv": "^16.3.0",
    "express": "^4.18.2",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/cors": "^2.8.17",
    "@types/express": "^4.17.21",
    "@types/node": "^20.10.0",
    "nodemon": "^3.0.2",
    "tsx": "^4.7.0",
    "typescript": "^5.3.0"
  }
}

```

### web/src/main.tsx

```typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import './styles/tokens.css'
import './styles/base.css'
import './styles/components.css'
import './styles/lowVision.css'
import './styles/theme.css'
import './styles/accessibility.css'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
)

```

### web/src/App.tsx

```typescript
import { Routes, Route } from 'react-router-dom'
import { Shell } from './components/Shell'
import { CompareModeProvider } from './components/CompareModeContext'
import { Landing } from './routes/Landing'
import { Home } from './routes/Home'
import { Open } from './routes/Open'
import { Extract } from './routes/Extract'
import { ExtractSearch } from './routes/ExtractSearch'
import { Passport } from './routes/Passport'
import { Compare } from './routes/Compare'
import { Preferences } from './routes/Preferences'
import { SearchReader } from './routes/SearchReader'
import { Unsupported } from './routes/Unsupported'
import { NotFound } from './routes/NotFound'

function App() {
  return (
    <CompareModeProvider>
      <Shell>
      <Routes>
        <Route path="/" element={<Landing />} />
        <Route path="/demo" element={<Home />} />
        <Route path="/open" element={<Open />} />
        <Route path="/extract" element={<Extract />} />
        <Route path="/extract-search" element={<ExtractSearch />} />
        <Route path="/p/:id" element={<Passport />} />
        <Route path="/compare" element={<Compare />} />
        <Route path="/preferences" element={<Preferences />} />
        <Route path="/s/:id" element={<SearchReader />} />
        <Route path="/unsupported" element={<Unsupported />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Shell>
    </CompareModeProvider>
  )
}

export default App

```

### backend/src/server.ts

```typescript
import express from "express";
import { config, validateEnv } from "./config/env";
import { corsMiddleware, isAllowedOrigin } from "./middleware/cors";
import { errorHandler } from "./middleware/errorHandler";
import healthRoutes from "./routes/health";
import productRoutes from "./routes/products";
import searchRoutes from "./routes/search";
import { logger } from "./utils/logger";

// Validate environment variables
try {
  validateEnv();
} catch (error) {
  logger.error("Environment validation failed", {
    error: error instanceof Error ? error.message : String(error)
  });
  process.exit(1);
}

// Create Express app
const app = express();

// Explicit OPTIONS handler FIRST - must return 204 for preflight to pass CORS check
app.options("*", (req, res) => {
  const origin = req.headers.origin;
  const allowOrigin = origin && isAllowedOrigin(origin) ? origin : "https://alt-cart.vercel.app";
  res.setHeader("Access-Control-Allow-Origin", allowOrigin);
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
  res.setHeader("Access-Control-Allow-Credentials", "true");
  res.setHeader("Access-Control-Max-Age", "86400");
  res.status(204).end();
});

// CORS for non-OPTIONS requests
app.use(corsMiddleware);

app.use(express.json());

// Request logging
app.use((req, res, next) => {
  logger.info("Incoming request", {
    method: req.method,
    path: req.path,
    ip: req.ip,
  });
  next();
});

// Routes
app.use("/api/health", healthRoutes);
app.use("/api/products", productRoutes);
app.use("/api/search", searchRoutes);

// Error handling
app.use(errorHandler);

// Start server (skip on Vercel - it uses the exported app)
const PORT = config.port;

if (process.env.VERCEL !== "1") {
  app.listen(PORT, () => {
    logger.info(`alt+cart backend server running on port ${PORT}`);
    logger.info(`Environment: ${config.nodeEnv}`);
    logger.info(`Frontend URL: ${config.frontendUrl}`);
  });

  process.on("SIGTERM", () => {
    logger.info("SIGTERM received, shutting down gracefully");
    process.exit(0);
  });

  process.on("SIGINT", () => {
    logger.info("SIGINT received, shutting down gracefully");
    process.exit(0);
  });
}

export default app;

```

### web/vite.config.ts

```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

```

### web/vite.config.js

```javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
    plugins: [react()],
});

```

### web/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
  { ignores: ['dist', 'public/webpages'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
)

```

### extension/popup.js

```javascript
/**
 * Popup init: load saved state and attach event handlers.
 * Uses addEventListener (required by extension CSP - no inline handlers).
 */
(function () {
  var toggle = document.getElementById("hover-speak-toggle");
  var openBtn = document.getElementById("open-btn");

  if (toggle) {
    chrome.storage.sync.get({ hoverSpeakEnabled: true }, function (stored) {
      if (toggle) toggle.checked = stored.hoverSpeakEnabled;
    });
    toggle.addEventListener("change", function () {
      chrome.storage.sync.set({ hoverSpeakEnabled: toggle.checked });
    });
  }

  if (openBtn) {
    openBtn.addEventListener("mousedown", function (e) {
      e.preventDefault();
      chrome.runtime.sendMessage({ type: "OPEN_PASSPORT" }, function () {
        window.close();
      });
    });
  }
})();

```

[90 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]