# Project export: AccessGuard AI

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: OpenAI Build Week
- Tagline: An AI-powered accessibility auditor that finds website barriers, explains their impact, and generates fixes for developers.
- Devpost: https://devpost.com/software/accessguard-ai
- GitHub: https://github.com/sajjalfatima264-wq/AccessGuard-AI
- Video: https://www.youtube.com/embed/8kM69lvwosE?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 0 GitHub contributor(s) — 

## Devpost submission (written by the team)

### Inspiration

Web accessibility is often treated as a checklist rather than a development problem. Existing accessibility tools can detect violations, but many developers struggle to understand why an issue matters, who it affects, and how to fix it. AccessGuard AI was created to bridge that gap by transforming accessibility auditing from a simple error report into a developer-focused assistant. The goal was to build a tool that not only identifies accessibility problems but also explains their impact and provides actionable guidance developers can use immediately.

### What it does

AccessGuard AI analyzes websites for common WCAG accessibility issues. A developer enters a website URL, and the system: Crawls the webpage using Playwright. Extracts and analyzes the rendered HTML structure. Parses semantic information using BeautifulSoup. Applies accessibility rules based on WCAG guidelines. Groups similar violations to reduce report noise. Generates a detailed accessibility report with: Issue type Severity WCAG reference Affected elements User impact explanation Recommended fixes Issue type Severity WCAG reference Affected elements User impact explanation Recommended fixes The result is a developer-friendly dashboard that helps teams understand and improve website accessibility.

### How we built it

AccessGuard AI was built using a full-stack architecture: React + Vite + Tailwind CSS for the frontend dashboard. FastAPI for backend APIs. Playwright for browser-based website analysis. BeautifulSoup for HTML semantic parsing. A modular explanation layer designed for future LLM integration. The project follows a deterministic-first architecture where accessibility validation is handled through reliable rules, while AI capabilities can be added as an enhancement layer without changing the core scanning pipeline. Challenges One of the biggest challenges was designing a reliable analysis pipeline that could handle real websites. We worked through challenges including: Extracting meaningful information from dynamic websites. Handling inconsistent HTML structures. Creating accurate accessibility checks. Avoiding duplicate issues in reports. Building a scoring system that reflects accessibility performance. Designing the application so future AI models can enhance explanations without replacing the core logic.

### What we learned

Through building AccessGuard AI, we learned the importance of separating reliable engineering foundations from AI augmentation. Instead of relying entirely on an AI model, we created a structured accessibility analysis pipeline where AI can provide additional intelligence while the underlying system remains predictable and explainable. Future Improvements Future versions can include: LLM-powered accessibility explanations. Automated code fix generation. Multi-page website crawling. Automated color contrast analysis. Keyboard navigation testing. Persistent accessibility reports and trend tracking.

## README (from the GitHub repository)

# AccessGuard AI

An AI-assisted accessibility auditing tool that helps developers identify, understand, and fix common web accessibility issues.

AccessGuard AI analyzes websites against WCAG accessibility guidelines, detects violations, groups repeated issues, calculates accessibility scores, and provides developer-friendly explanations with implementation guidance.

---

## Problem

Millions of websites still contain accessibility barriers that prevent people with disabilities from fully accessing digital content.

While existing accessibility scanners can detect issues, they often generate long technical reports that make it difficult for developers to understand:

- What is wrong?
- Why does it matter?
- Who is affected?
- How can it be fixed?

AccessGuard AI transforms accessibility testing into an understandable developer workflow by combining automated analysis with clear explanations and actionable recommendations.

---

# Solution

AccessGuard AI provides an end-to-end accessibility auditing pipeline that:

- Crawls websites
- Extracts accessibility-related HTML elements
- Evaluates them against WCAG guidelines
- Groups duplicate violations
- Calculates accessibility scores
- Generates developer-friendly explanations and implementation guidance

Instead of simply listing accessibility errors, AccessGuard AI helps developers understand their impact and how to resolve them.

---

# Features

## Website Accessibility Scanning

Scans webpages using Playwright and extracts semantic HTML elements for accessibility analysis.

The current implementation evaluates:

- Images
- Forms
- Headings
- Buttons
- Links

---

## WCAG Rule Engine

Detects common accessibility issues including:

- Missing alternative text
- Incorrect heading hierarchy
- Unlabeled form controls
- Empty or unclear links
- Buttons without accessible labels

Each issue includes:

- WCAG Success Criterion
- Conformance Level
- Severity
- Affected Elements

Example:

```
WCAG 1.1.1
Level A
Missing alternative text
```

---

## Smart Issue Grouping

Large websites often contain many identical accessibility violations.

AccessGuard AI groups duplicate issues into a single report showing:

- Number of occurrences
- Example affected elements
- WCAG reference

This keeps reports concise and easier to understand.

---

## Accessibility Scoring

Accessibility scores are calculated using:

```
Passed Checks / Total Checks × 100
```

The dashboard displays:

- Overall accessibility score
- Category scores
- Total checks
- Failed checks

Example:

```
Overall Score: 92%

Images: 80%
Forms: 100%
Headings: 90%
Buttons: 100%
Links: 95%
```

---

## AI-Assisted Explanation Engine

Each detected issue includes a structured explanation consisting of:

### Problem

Explains what is wrong.

### User Impact

Describes how users may be affected.

### Recommended Solution

Provides practical remediation guidance.

### Corrected Implementation

Shows an example implementation developers can follow.

The explanation module is intentionally isolated from the scanning engine, making it straightforward to extend with future LLM-powered capabilities.

---

# Architecture

```
             Website URL
                  │
                  ▼
        Playwright Web Crawler
                  │
                  ▼
      HTML Accessibility Parser
                  │
                  ▼
         WCAG Rules Engine
                  │
                  ▼
      Issue Deduplication Layer
                  │
                  ▼
      Accessibility Score Engine
                  │
                  ▼
     Explanation Generation Layer
                  │
                  ▼
        React Dashboard Interface
```

---

# Tech Stack

## Frontend

- React
- Vite
- Tailwind CSS

## Backend

- Python
- FastAPI
- Uvicorn

## Accessibility Engine

- Playwright
- BeautifulSoup
- Custom WCAG Rule Engine

## Testing

- Pytest

## Containerization

- Docker

---

# Project Structure

```
AccessGuard-AI/

├── backend/
│   ├── app/
│   │   ├── ai/
│   │   ├── services/
│   │   ├── routes/
│   │   └── main.py
│   │
│   ├── tests/
│   ├── requirements.txt
│   └── Dockerfile
│
├── frontend/
│   ├── src/
│   ├── public/
│   ├── package.json
│   └── Dockerfile
│
├── docker-compose.yml
├── LICENSE
└── README.md
```

---

# Installation

## Prerequisites

- Python 3.10+
- Node.js 18+
- Chromium (installed automatically through Playwright)

---

## Backend

```bash
cd backend

python -m venv venv

source venv/bin/activate

pip install -r requirements.txt

playwright install chromium

uvicorn app.main:app --reload
```

Backend:

```
http://localhost:8000
```

---

## Frontend

```bash
cd frontend

npm install

npm run dev
```

Frontend:

```
http://localhost:5173
```

---

# Testing

Run the complete backend test suite:

```bash
cd backend

source venv/bin/activate

pytest tests/ -v
```

Current automated tests cover:

- Accessibility rule validation
- Issue grouping
- Accessibility scoring
- Parser resilience
- Report schema validation

---

# Supported Platforms

AccessGuard AI has been developed and tested on:

- macOS
- Linux
- Windows (WSL recommended)

---

# Testing Instructions

To evaluate the project:

1. Start the backend server.
2. Start the frontend application.
3. Open the dashboard.
4. Enter a public website URL.
5. Run an accessibility scan.
6. Review:

- Accessibility score
- WCAG issues
- AI-assisted explanations
- Suggested implementation guidance

---

# Development Process: Codex & GPT-5.6

## How Codex Accelerated Development

AccessGuard AI was developed using Codex as an AI coding assistant throughout the project.

Rather than generating the application end-to-end, Codex accelerated iterative development by assisting with:

- Planning the overall project architecture
- Bootstrapping the React and FastAPI applications
- Implementing backend service modules
- Refactoring code into reusable components
- Debugging frontend and backend integration issues
- Writing and refining automated tests
- Reviewing code quality and suggesting improvements

Development followed an incremental milestone-based workflow:

1. Project setup and architecture
2. Backend API implementation
3. Website crawling with Playwright
4. HTML parsing and semantic extraction
5. WCAG rule engine
6. Accessibility scoring
7. Frontend dashboard
8. Testing and refinement

Each milestone was implemented, tested, and reviewed before moving to the next stage.

---

## How GPT-5.6 Was Used

GPT-5.6 acted as a technical design and engineering assistant during development.

It was used to:

- Explore architectural alternatives
- Review implementation decisions
- Debug backend and frontend issues
- Improve API design
- Refactor code for maintainability
- Review accessibility analysis logic
- Suggest improvements to testing and project structure
- Help prepare documentation and project presentation

All implementation decisions were reviewed before being incorporated into the final project.

---

# Key Engineering Decisions

## Deterministic Accessibility Analysis

Accessibility evaluation requires predictable and reproducible results.

For this reason, the core auditing pipeline is deterministic rather than LLM-driven.

Website crawling, HTML parsing, WCAG rule evaluation, and accessibility scoring are implemented as dedicated modules with explicit logic.

This provides consistent, repeatable results regardless of external AI services.

---

## Modular AI-Ready Architecture

The explanation layer is intentionally isolated from the scanning pipeline.

Separating explanation generation from accessibility analysis allows future integration of LLM-based capabilities without modifying the core auditing engine.

Potential future enhancements include:

- Context-aware accessibility explanations
- Framework-specific implementation examples
- AI-generated remediation guidance
- Personalized developer recommendations

This modular architecture keeps the accessibility scanner r

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 31 recognized source files, 66 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (37 of 37)

```
.gitignore
backend/app/__init__.py
backend/app/ai/__init__.py
backend/app/ai/explanation.py
backend/app/config.py
backend/app/main.py
backend/app/routes/__init__.py
backend/app/routes/health.py
backend/app/routes/scan.py
backend/app/services/crawler.py
backend/app/services/grouper.py
backend/app/services/parser.py
backend/app/services/rules.py
backend/app/services/scanner.py
backend/app/services/scorer.py
backend/Dockerfile
backend/requirements.txt
backend/tests/__init__.py
backend/tests/test_grouper.py
backend/tests/test_parser.py
backend/tests/test_report.py
backend/tests/test_rules.py
backend/tests/test_scorer.py
docs/.gitkeep
frontend/Dockerfile
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/App.css
frontend/src/App.jsx
frontend/src/index.css
frontend/src/main.jsx
frontend/tailwind.config.js
frontend/vite.config.js
README.md
```

### Dependencies

- backend/requirements.txt: beautifulsoup4@==4.12.3, fastapi, playwright@==1.48.0, pydantic-settings, python-dotenv, uvicorn[standard]
- frontend/package.json: @eslint/js@^9.13.0, @types/react@^18.3.12, @types/react-dom@^18.3.1, @vitejs/plugin-react@^4.3.3, autoprefixer@^10.5.2, eslint@^9.13.0, eslint-plugin-react@^7.37.2, eslint-plugin-react-hooks@^5.0.0, eslint-plugin-react-refresh@^0.4.14, globals@^15.11.0, postcss@^8.5.19, react@^18.3.1, react-dom@^18.3.1, tailwindcss@^3.4.19, vite@^6.4.3

### Recent commits (newest first)

- Create README.md
- front end
- backend
- Delete docker-compose.yml
- Delete Screen Shot 2026-07-24 at 11.45.24 AM.png
- Delete Screen Shot 2026-07-15 at 1.28.14 PM.png
- Delete Screen Shot 2026-07-15 at 1.19.57 PM.png
- Delete frontend directory
- Delete backend directory
- Remove legacy V1.0 entrypoint
- V2.0: AccessGuard AI MVP - Scoring, Grouping, WCAG Mapping

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

### backend/requirements.txt

```
fastapi
uvicorn[standard]
python-dotenv
pydantic-settings
playwright==1.48.0
beautifulsoup4==4.12.3

```

### frontend/Dockerfile

```
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

```

### backend/Dockerfile

```
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y wget gnupg2 && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN playwright install --with-deps chromium
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.13.0",
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react": "^4.3.3",
    "autoprefixer": "^10.5.2",
    "eslint": "^9.13.0",
    "eslint-plugin-react": "^7.37.2",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.14",
    "globals": "^15.11.0",
    "postcss": "^8.5.19",
    "tailwindcss": "^3.4.19",
    "vite": "^6.4.3"
  }
}

```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

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

```

### backend/app/main.py

```python
from urllib.parse import urlparse
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict
from uuid import uuid4
import re

from app.services.crawler import crawl_website
from app.services.parser import parse_html_to_accessibility_data
from app.services.rules import run_accessibility_checks
from app.services.grouper import group_and_deduplicate_issues
from app.services.scorer import calculate_accurate_scores
from app.ai.explanation import generate_ai_explanations

class Settings(BaseSettings):
    frontend_url: str = "http://localhost:5173"
    crawler_timeout: int = 30
    model_config = SettingsConfigDict(extra="ignore")

settings = Settings()
SCAN_DATABASE = {}

app = FastAPI(title="AccessGuard AI", description="AI-ready accessibility auditing assistant MVP.", version="2.0.0")
app.add_middleware(CORSMiddleware, allow_origins=[settings.frontend_url], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])

class ScanRequest(BaseModel):
    url: str

class ElementModel(BaseModel):
    element: str
    location: str

class IssueModel(BaseModel):
    id: str
    type: str
    wcag_reference: str
    level: str
    severity: str
    description: str
    occurrences: int
    elements: list[ElementModel]
    ai: dict | None = None

class CategoryScoreModel(BaseModel):
    score: int
    total: int
    failed: int

class ScoreModel(BaseModel):
    overall: int
    categories: dict[str, CategoryScoreModel]

class ReportModel(BaseModel):
    score: ScoreModel
    issues: list[IssueModel]

class ScanResponse(BaseModel):
    scan_id: str
    url: str
    status: str
    report: ReportModel

def sanitize_html(html: str) -> str:
    return html.replace("<", "&lt;").replace(">", "&gt;")[:100]

def is_valid_url(url: str) -> bool:
    url = re.sub(r"^[^:]*@", "", url) # Strip user:pass@
    if not re.match(r"^https?://", url): return False
    try:
        parsed = urlparse(url)
        if not parsed.hostname: return False
        if parsed.hostname in ["localhost", "127.0.0.1", "0.0.0.0"]: return False
        if parsed.hostname.startswith(("192.168.", "10.", "172.", "169.254.")): return False
        if parsed.scheme not in ["http", "https"]: return False
    except Exception:
        return False
    return True

@app.get("/health", tags=["System"])
def health_check():
    return {"status": "ok", "service": "AccessGuard AI API"}

@app.post("/api/scan", response_model=ScanResponse, tags=["Scanning"])
async def request_scan(request: ScanRequest):
    if not is_valid_url(request.url):
        raise HTTPException(status_code=400, detail="Invalid, unsafe, or unsupported URL.")

    try:
        raw_data = await crawl_website(request.url, timeout=settings.crawler_timeout)
        
        # CRAWLER -> PARSER VALIDATION
        html_content = raw_data.get("html")
        if not html_content or len(html_content.strip()) < 50:
            return JSONResponse(
                status_code=500,
                content={"error": "Unable to analyze website", "reason": "Page returned empty or invalid HTML"}
            )

        parsed_data = parse_html_to_accessibility_data(html_content)
        
        # PARSER VALIDATION
        if not isinstance(parsed_data, dict):
            return JSONResponse(
                status_code=500,
                content={"error": "Unable to analyze website", "reason": "Failed to parse page structure"}
            )

        raw_issues = run_accessibility_checks(parsed_data)
        grouped_issues = group_and_deduplicate_issues(raw_issues)
        issues_with_ai = await generate_ai_explanations(grouped_issues)
        
        scores = calculate_accurate_scores(parsed_data, issues_with_ai)
        
        for issue in issues_with_ai:
            for el in issue.get("elements", []):
                el["element"] = sanitize_html(el["element"])
            if issue.get("ai") and issue["ai"].get("code_example"):
                issue["ai"]["code_example"] = sanitize_html(issue["ai"]["code_example"])
                
        report = ReportModel(score=scores, issues=issues_with_ai)
        scan_id = str(uuid4())
        SCAN_DATABASE[scan_id] = report
        
        return ScanResponse(scan_id=scan_id, url=request.url.rstrip("/"), status="success", report=report)
    
    except Exception as e:
        error_msg = str(e)
        reason = "Unknown error occurred during scanning"
        if "Timeout" in error_msg: reason = "Timeout while loading page"
        elif "Invalid website" in error_msg: reason = "Domain not found"
        elif "Connection refused" in error_msg: reason = "Server refused connection"
        elif "Failed to crawl" in error_msg: reason = "Failed to crawl website"
        
        return JSONResponse(
            status_code=500,
            content={"error": "Unable to analyze website", "reason": reason}
        )

@app.get("/api/report/{scan_id}", response_model=ReportModel, tags=["Reporting"])
async def get_report(scan_id: str):
    if scan_id not in SCAN_DATABASE:
        raise HTTPException(status_code=404, detail="Report not found.")
    return SCAN_DATABASE[scan_id]
```

### frontend/src/App.jsx

```javascript
import { useState } from 'react';

// --- Icons ---
const SparklesIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
    <path d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.894.553l1.382 2.764 2.764 1.382a1 1 0 010 1.788l-2.764 1.382-1.382 2.764a1 1 0 01-1.788 0l-1.382-2.764-2.764-1.382a1 1 0 010-1.788l2.764-1.382L11.106 2.553A1 1 0 0112 2z" />
  </svg>
);

const CopyIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
    <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
    <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
  </svg>
);

const CheckIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6" viewBox="0 0 20 20" fill="currentColor">
    <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
  </svg>
);

// --- Components ---
const Loader = () => (
  <div className="flex flex-col items-center justify-center py-20 space-y-6">
    <div className="w-16 h-16 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
    <div className="text-center">
      <p className="text-white text-lg font-semibold">Initializing AccessGuard AI...</p>
      <p className="text-gray-500 text-sm mt-2">Crawling DOM • Running WCAG Rules • Generating Insights</p>
    </div>
  </div>
);

const CodeBlock = ({ code }) => {
  const [copied, setCopied] = useState(false);

  const handleCopy = () => {
    navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="relative group">
      <button
        onClick={handleCopy}
        className="absolute top-3 right-3 flex items-center gap-1.5 text-xs font-medium text-gray-400 bg-gray-800/80 hover:bg-gray-700 px-2.5 py-1.5 rounded-md transition-colors border border-gray-700"
      >
        {copied ? <CheckIcon /> : <CopyIcon />}
        {copied ? 'Copied!' : 'Copy'}
      </button>
      <pre className="bg-black/60 p-4 rounded-lg border border-gray-800 font-mono text-xs text-green-300 whitespace-pre-wrap overflow-x-auto max-h-64">
        {code}
      </pre>
    </div>
  );
};

const IssueCard = ({ issue }) => {
  const [isOpen, setIsOpen] = useState(false);

  const severityConfig = {
    Critical: { border: 'border-l-rose-500', badge: 'bg-rose-500/10 text-rose-400 border-rose-500/30', dot: 'bg-rose-500' },
    High: { border: 'border-l-orange-500', badge: 'bg-orange-500/10 text-orange-400 border-orange-500/30', dot: 'bg-orange-500' },
    Medium: { border: 'border-l-amber-500', badge: 'bg-amber-500/10 text-amber-400 border-amber-500/30', dot: 'bg-amber-500' },
  };

  const config = severityConfig[issue.severity] || severityConfig.Medium;
  const ai = issue.ai;

  return (
    <div className={`bg-gray-900/50 rounded-xl border border-gray-800 ${config.border} border-l-4 transition-all hover:border-gray-700 hover:shadow-xl hover:shadow-black/20`}>
      <div
        className="p-5 cursor-pointer flex justify-between items-center gap-4"
        onClick={() => setIsOpen(!isOpen)}
      >
        <div className="flex-1 space-y-2">
          <div className="flex items-center gap-3 flex-wrap">
            <span className="font-semibold text-white text-sm tracking-wide">{issue.type}</span>
            <span className="text-xs px-2 py-1 bg-gray-800/80 rounded-md font-mono text-gray-400 border border-gray-700/50">
              {issue.wcag_reference} • Level {issue.level}
            </span>
            <span className={`text-xs font-semibold px-2.5 py-1 rounded-full border flex items-center gap-1.5 ${config.badge}`}>
              <span className={`w-1.5 h-1.5 rounded-full ${config.dot}`}></span>
              {issue.severity}
            </span>
          </div>
          <p className="text-sm text-gray-400">{issue.description}</p>
          <div className="flex items-center gap-4 text-xs text-gray-500 font-mono pt-1">
            <span>{issue.occurrences} occurrences</span>
            <span className="text-gray-700">|</span>
            <span className="truncate">First seen: {issue.elements[0]?.location || 'N/A'}</span>
          </div>
        </div>
        <button className="text-gray-500 hover:text-white transition-colors p-2 rounded-full hover:bg-gray-800">
          <svg className={`w-5 h-5 transform transition-transform ${isOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
          </svg>
        </button>
      </div>

      {isOpen && (
        <div className="px-5 pb-5 border-t border-gray-800/80 pt-5 space-y-6">
          {/* Technical Info */}
          <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
            <div>
              <h6 className="text-xs font-semibold text-gray-300 mb-2 uppercase tracking-wider">Affected Elements</h6>
              <div className="space-y-2 max-h-40 overflow-y-auto pr-2">
                {issue.elements.slice(0, 5).map((el, i) => (
                  <div key={i} className="text-xs font-mono text-gray-400 bg-black/40 p-2.5 rounded border border-gray-800 break-all">
                    {el.element}
                  </div>
                ))}
                {issue.elements.length > 5 && (
                  <p className="text-xs text-gray-600 italic pt-1">+ {issue.elements.length - 5} more elements</p>
                )}
              </div>
            </div>
          </div>

          {/* AI Explanation Section */}
          {ai && (
            <div className="relative rounded-xl p-[1px] bg-gradient
[truncated — 14101 more characters]
```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/vite.config.js

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

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

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

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