# Jaak Stamps Web Component

[![npm version](https://badge.fury.io/js/%40jaak.ai%2Fstamps.svg)](https://www.npmjs.com/package/@jaak.ai/stamps)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

[📖 Versión en Español](./README_ES.md)

## Description

Jaak Stamps is a powerful web component for document scanning and capture using camera input with AI-powered document detection. The component provides a complete solution for capturing high-quality document images with real-time detection feedback and automated cropping capabilities.

### Key Features

- **AI-Powered Document Detection**: Automatically detects and frames documents in the camera view
- **Dual-Side Capture**: Supports both front and back document capture workflow
- **Real-time Preview**: Live camera feed with detection overlays and guidance
- **Multi-Camera Support**: Automatic camera selection with manual override options
- **Smart Cropping**: Automatic document boundary detection and cropping
- **Mobile Optimized**: Responsive design that works across all device sizes
- **Web Standards Compliant**: Built as a standard Web Component for maximum compatibility

### Typical Use Cases

- **Identity Verification**: Capture ID cards, passports, and driver's licenses
- **Document Digitization**: Scan contracts, certificates, and legal documents  
- **KYC/AML Processes**: Streamlined identity document capture for compliance
- **Form Processing**: Digitize paper forms and applications
- **Insurance Claims**: Capture damage reports and supporting documentation

## Installation

### NPM/Yarn

```bash
# Using npm
npm install @jaak.ai/stamps

# Using yarn
yarn add @jaak.ai/stamps
```

### CDN

```html
<!-- ES Module -->
<script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>

<!-- With Loader -->
<script type="module">
  import { defineCustomElements } from 'https://unpkg.com/@jaak.ai/stamps/loader/index.js';
  defineCustomElements();
</script>
```

## API Reference

### Properties/Attributes

#### License Properties (Required)

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `license` | `string` | `undefined` | **[REQUIRED]** License key for component authentication |
| `license-environment` | `'dev' \| 'qa' \| 'sandbox' \| 'prod'` | `'prod'` | API environment for license validation |
| `app-id` | `string` | `'jaak-stamps-web'` | Application identifier for analytics and tracking |
| `trace-id` | `string` | `undefined` | Optional trace ID for distributed tracing (auto-generated if not provided) |

#### Base Configuration Properties

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `debug` | `boolean` | `false` | Enable debug mode with additional logging and overlays |
| `mask-size` | `number` | `90` | Size percentage of the document detection mask (50-100) |
| `alignment-tolerance` | `number` | `15` | Tolerance level in pixels for document alignment detection |
| `capture-delay` | `number` | `1500` | Delay in milliseconds before automatic capture (0-10000) |
| `crop-margin` | `number` | `20` | Margin in pixels around detected document for cropping (0-100) |
| `preferred-camera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Preferred camera selection |
| `use-document-classification` | `boolean` | `false` | Enable AI document type classification |
| `use-document-detector` | `boolean` | `true` | Enable/disable AI document detection model |
| `enable-back-document-timer` | `boolean` | `false` | Enable timer for back document capture |
| `back-document-timer-duration` | `number` | `20` | Duration in seconds for back document capture timer |

#### Telemetry and OpenTelemetry Properties

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `telemetry-collector-url` | `string` | `'https://collector.jaak.ai/v1/traces'` | OTLP collector URL for distributed traces |
| `metrics-collector-url` | `string` | `'https://collector.jaak.ai/v1/metrics'` | OTLP collector URL for metrics |
| `enable-telemetry` | `boolean` | `true` | Enable distributed tracing with OpenTelemetry |
| `enable-metrics` | `boolean` | `true` | Enable metrics export to OpenTelemetry |
| `metrics-export-interval-millis` | `number` | `60000` | Metrics export interval in milliseconds |
| `propagate-trace-header-cors-urls` | `string` | `undefined` | Comma-separated URLs for W3C Trace Context header propagation |

#### Context Properties

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `customer-id` | `string` | `undefined` | Customer ID for telemetry and analytics |
| `tenant-id` | `string` | `undefined` | Tenant ID for multi-tenancy support |
| `environment` | `string` | `'production'` | Execution environment (development/staging/production) |

### Methods

#### Camera Control

```typescript
// Get information about available cameras
getCameraInfo(): Promise<CameraInfoResponse>

// Set preferred camera
setPreferredCamera(camera: "auto" | "front" | "back"): Promise<{
  success: boolean;
  selectedCamera: string;
  availableCameras: number;
}>

// Control device torch/flashlight
setTorchEnabled(enabled: boolean): Promise<{
  success: boolean;
  enabled: boolean;
}>

// Focus camera at specific point
focusAtPoint(x: number, y: number): Promise<{
  success: boolean;
  coordinates: { x: number; y: number };
}>
```

#### Capture Control

```typescript
// Start the capture process
startCapture(): Promise<void>

// Stop the capture process
stopCapture(): Promise<void>

// Reset capture state
resetCapture(): Promise<void>

// Skip back document capture
skipBackCapture(): Promise<void>
```

#### Configuration

```typescript
// Set capture delay
setCaptureDelay(delay: number): Promise<{
  success: boolean;
  captureDelay: number;
}>

// Get current capture delay
getCaptureDelay(): Promise<number>
```

#### Status & Data

```typescript
// Get current component status
getStatus(): Promise<StatusResponse>

// Get captured images
getCapturedImages(): Promise<CapturedImagesResponse>

// Check if process is completed
isProcessCompleted(): Promise<boolean>

// Preload AI model
preloadModel(): Promise<{
  success: boolean;
  message?: string;
  error?: any;
}>
```

### Events

| Event | Detail Type | Description |
|-------|-------------|-------------|
| `isReady` | `boolean` | Fired when component is fully initialized and ready |
| `captureCompleted` | `object` | Fired when document capture process is completed |
| `traceIdGenerated` | `{ traceId: string }` | Fired after license validation with the generated or provided trace ID for request tracing |

### Type Definitions

```typescript
interface CameraInfoResponse {
  availableCameras: Array<{
    id: string;
    label: string;
    selected: boolean;
  }>;
  selectedCameraId: string | null;
  deviceType: string;
  isMultipleCamerasAvailable: boolean;
  preferredFacing: 'environment' | 'user' | null;
}

interface CapturedImagesResponse {
  front: {
    fullFrame: string | null;
    cropped: string | null;
  };
  back: {
    fullFrame: string | null;
    cropped: string | null;
  };
  metadata: {
    totalImages: number;
    processCompleted: boolean;
    backCaptureSkipped: boolean;
  };
}

interface StatusResponse {
  isVideoActive: boolean;
  captureStep: 'front' | 'back' | 'completed';
  hasImages: boolean;
  isProcessCompleted: boolean;
  isModelPreloaded: boolean;
}
```

## Implementation Examples

### Vanilla JavaScript

```html
<!DOCTYPE html>
<html>
<head>
  <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
</head>
<body>
  <jaak-stamps
    id="documentScanner"
    license="your-license-key-here"
    environment="prod"
    app-id="my-custom-app"
    preferred-camera="auto"
    capture-delay="1500"
    use-document-detector="true"
    debug="false">
  </jaak-stamps>

  <script>
    const scanner = document.getElementById('documentScanner');

    // Handle trace ID generation
    scanner.addEventListener('traceIdGenerated', (event) => {
      console.log('Trace ID for this session:', event.detail.traceId);
      // Use this trace ID for correlating requests across your system
    });

    // Wait for component to be ready
    scanner.addEventListener('isReady', () => {
      console.log('Document scanner is ready');

      // Start the capture process
      scanner.startCapture();
    });

    // Handle capture completion
    scanner.addEventListener('captureCompleted', async (event) => {
      console.log('Capture completed:', event.detail);

      // Get captured images
      const images = await scanner.getCapturedImages();
      console.log('Captured images:', images);

      // Process the images as needed
      if (images.front.cropped) {
        // Display or upload the cropped front image
        displayImage(images.front.cropped);
      }
    });
    
    function displayImage(base64Image) {
      const img = document.createElement('img');
      img.src = `data:image/jpeg;base64,${base64Image}`;
      document.body.appendChild(img);
    }
  </script>
</body>
</html>
```

### React

```tsx
import React, { useEffect, useRef, useState } from 'react';
import { defineCustomElements } from '@jaak.ai/stamps/loader';

// Define custom elements
defineCustomElements();

// Extend HTMLElement for TypeScript
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'jaak-stamps': any;
    }
  }
}

interface CapturedImages {
  front: { fullFrame: string | null; cropped: string | null };
  back: { fullFrame: string | null; cropped: string | null };
  metadata: {
    totalImages: number;
    processCompleted: boolean;
    backCaptureSkipped: boolean;
  };
}

const DocumentScanner: React.FC = () => {
  const scannerRef = useRef<HTMLElement>(null);
  const [isReady, setIsReady] = useState(false);
  const [capturedImages, setCapturedImages] = useState<CapturedImages | null>(null);
  
  useEffect(() => {
    const scanner = scannerRef.current;
    if (!scanner) return;
    
    // Trace ID generation handler
    const handleTraceIdGenerated = (event: CustomEvent) => {
      console.log('Trace ID:', event.detail.traceId);
    };

    // Component ready handler
    const handleReady = () => {
      setIsReady(true);
    };

    // Capture completion handler
    const handleCaptureCompleted = async () => {
      const images = await (scanner as any).getCapturedImages();
      setCapturedImages(images);
    };

    // Add event listeners
    scanner.addEventListener('traceIdGenerated', handleTraceIdGenerated);
    scanner.addEventListener('isReady', handleReady);
    scanner.addEventListener('captureCompleted', handleCaptureCompleted);
    
    return () => {
      scanner.removeEventListener('traceIdGenerated', handleTraceIdGenerated);
      scanner.removeEventListener('isReady', handleReady);
      scanner.removeEventListener('captureCompleted', handleCaptureCompleted);
    };
  }, []);
  
  const startScan = async () => {
    if (scannerRef.current) {
      await (scannerRef.current as any).startCapture();
    }
  };
  
  return (
    <div>
      <h2>Document Scanner</h2>
      
      <jaak-stamps
        ref={scannerRef}
        license="your-license-key-here"
        environment="prod"
        app-id="my-react-app"
        trace-id=""  // Leave empty to auto-generate
        preferred-camera="auto"
        capture-delay={1500}
        use-document-classification={true}
        use-document-detector={true}
      />
      
      <div style={{ marginTop: '20px' }}>
        <button onClick={startScan} disabled={!isReady}>
          {isReady ? 'Start Scanning' : 'Loading...'}
        </button>
      </div>
      
      {capturedImages && (
        <div style={{ marginTop: '20px' }}>
          <h3>Captured Images</h3>
          {capturedImages.front.cropped && (
            <div>
              <h4>Front Document</h4>
              <img 
                src={`data:image/jpeg;base64,${capturedImages.front.cropped}`}
                alt="Front document"
                style={{ maxWidth: '300px' }}
              />
            </div>
          )}
          {capturedImages.back.cropped && (
            <div>
              <h4>Back Document</h4>
              <img 
                src={`data:image/jpeg;base64,${capturedImages.back.cropped}`}
                alt="Back document"
                style={{ maxWidth: '300px' }}
              />
            </div>
          )}
        </div>
      )}
    </div>
  );
};

export default DocumentScanner;
```

### Angular

```typescript
// document-scanner.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { defineCustomElements } from '@jaak.ai/stamps/loader';

// Define custom elements
defineCustomElements();

interface CapturedImages {
  front: { fullFrame: string | null; cropped: string | null };
  back: { fullFrame: string | null; cropped: string | null };
  metadata: {
    totalImages: number;
    processCompleted: boolean;
    backCaptureSkipped: boolean;
  };
}

@Component({
  selector: 'app-document-scanner',
  template: `
    <div class="scanner-container">
      <h2>Document Scanner</h2>
      
      <jaak-stamps
        #scanner
        license="your-license-key-here"
        environment="prod"
        app-id="my-angular-app"
        preferred-camera="auto"
        [capture-delay]="1500"
        [use-document-classification]="true"
        [use-document-detector]="true">
      </jaak-stamps>
      
      <div class="controls">
        <button 
          (click)="startScan()" 
          [disabled]="!isReady"
          class="scan-button">
          {{ isReady ? 'Start Scanning' : 'Loading...' }}
        </button>
        
        <button 
          (click)="resetScan()" 
          [disabled]="!isReady"
          class="reset-button">
          Reset
        </button>
      </div>
      
      <div *ngIf="capturedImages" class="results">
        <h3>Captured Images</h3>
        
        <div *ngIf="capturedImages.front.cropped" class="image-result">
          <h4>Front Document</h4>
          <img 
            [src]="'data:image/jpeg;base64,' + capturedImages.front.cropped"
            alt="Front document"
            class="captured-image">
        </div>
        
        <div *ngIf="capturedImages.back.cropped" class="image-result">
          <h4>Back Document</h4>
          <img 
            [src]="'data:image/jpeg;base64,' + capturedImages.back.cropped"
            alt="Back document"
            class="captured-image">
        </div>
        
        <div class="metadata">
          <p>Total Images: {{ capturedImages.metadata.totalImages }}</p>
          <p>Process Completed: {{ capturedImages.metadata.processCompleted }}</p>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .scanner-container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    
    .controls {
      margin: 20px 0;
      display: flex;
      gap: 10px;
    }
    
    .scan-button, .reset-button {
      padding: 10px 20px;
      font-size: 16px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .scan-button {
      background-color: #007bff;
      color: white;
    }
    
    .reset-button {
      background-color: #6c757d;
      color: white;
    }
    
    .scan-button:disabled, .reset-button:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
    
    .results {
      margin-top: 20px;
    }
    
    .image-result {
      margin: 20px 0;
    }
    
    .captured-image {
      max-width: 100%;
      height: auto;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    
    .metadata {
      background-color: #f8f9fa;
      padding: 10px;
      border-radius: 4px;
      margin-top: 20px;
    }
  `],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class DocumentScannerComponent implements AfterViewInit {
  @ViewChild('scanner', { static: false }) scannerRef!: ElementRef;
  
  isReady = false;
  capturedImages: CapturedImages | null = null;
  
  ngAfterViewInit() {
    const scanner = this.scannerRef.nativeElement;

    // Trace ID generation handler
    scanner.addEventListener('traceIdGenerated', (event: CustomEvent) => {
      console.log('Trace ID:', event.detail.traceId);
    });

    // Component ready handler
    scanner.addEventListener('isReady', () => {
      this.isReady = true;
    });

    // Capture completion handler
    scanner.addEventListener('captureCompleted', async () => {
      this.capturedImages = await scanner.getCapturedImages();
    });
  }
  
  async startScan() {
    if (this.scannerRef?.nativeElement) {
      await this.scannerRef.nativeElement.startCapture();
    }
  }
  
  async resetScan() {
    if (this.scannerRef?.nativeElement) {
      await this.scannerRef.nativeElement.resetCapture();
      this.capturedImages = null;
    }
  }
}
```

```typescript
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DocumentScannerComponent } from './document-scanner.component';

@NgModule({
  declarations: [DocumentScannerComponent],
  imports: [BrowserModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // Required for custom elements
  exports: [DocumentScannerComponent]
})
export class DocumentScannerModule { }
```

## Telemetry and Observability (OpenTelemetry)

Jaak Stamps includes full **OpenTelemetry** integration for distributed tracing, metrics, and performance monitoring in production environments.

### Distributed Tracing

The component implements distributed tracing following the **W3C Trace Context** standard, providing:

- Complete flow tracking of document capture processes
- Request correlation between frontend and backend
- Performance bottleneck identification
- Context propagation via `traceparent` headers

**Automatically Recorded Spans:**
- `component.initialize` - Component initialization
- `capture.start` - Capture process start
- `model.preload` - AI model preloading
- `model.detection.load` - Detection model loading
- `model.classification.load` - Classification model loading

### Performance Metrics

The component exports detailed metrics to OpenTelemetry:

**Counters:**
- `capture.counter` - Number of completed captures
- `error.counter` - Error count by type
- `model.load.counter` - Model load count
- `user.interaction.counter` - User interactions

**Histograms (Latencies):**
- `capture.latency` - Total capture time
- `model.load.latency` - Model loading time
- `detection.latency` - Per-frame detection time
- `image.size` - Captured image sizes

**Gauges (Current State):**
- `active.sessions` - Active sessions
- `memory.usage` - Memory usage

### Telemetry Configuration

```html
<jaak-stamps
  license="your-license-key"
  enable-telemetry="true"
  enable-metrics="true"
  telemetry-collector-url="https://collector.jaak.ai/v1/traces"
  metrics-collector-url="https://collector.jaak.ai/v1/metrics"
  metrics-export-interval-millis="60000"
  propagate-trace-header-cors-urls="https://api.example.com,https://backend.example.com"
  customer-id="customer123"
  tenant-id="tenant456"
  environment="production"
  trace-id="optional-custom-trace-id">
</jaak-stamps>
```

### Using Trace ID

The component automatically generates a unique trace ID for each session, or accepts a custom one:

```javascript
const scanner = document.getElementById('documentScanner');

// Listen for trace ID generation
scanner.addEventListener('traceIdGenerated', (event) => {
  const traceId = event.detail.traceId;
  console.log('Trace ID for this session:', traceId);

  // Use this traceId to correlate requests in your backend
  fetch('/api/process-document', {
    method: 'POST',
    headers: {
      'X-Trace-Id': traceId,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ /* data */ })
  });
});
```

### Enriched Attributes

Traces and metrics automatically include:

- **User Agent:** Browser, version, operating system
- **Geolocation:** Country, region, city (when available)
- **Device Memory:** Device memory capacity
- **Customer Context:** `customerId`, `tenantId`, `environment`
- **Service Info:** Service name, component version

### Context Propagation (CORS)

To propagate traces to your backend services:

```html
<jaak-stamps
  propagate-trace-header-cors-urls="https://api.example.com,https://services.example.com">
</jaak-stamps>
```

This automatically configures `fetch` and `XMLHttpRequest` interceptors to include the `traceparent` header in requests to those URLs.

### Visualization with Jaeger/Zipkin

Exported traces are compatible with:
- **Jaeger** - Distributed tracing system
- **Zipkin** - Alternative tracing system
- **Grafana Tempo** - Grafana trace backend
- Any OTLP-compatible backend (OpenTelemetry Protocol)

## License Validation

The component **requires a valid license** to function. Validation occurs automatically when the component loads.

### License Configuration

```html
<jaak-stamps
  license="your-license-key-here"
  license-environment="prod"
  app-id="my-application">
</jaak-stamps>
```

### Available Environments

| Environment | Validation URL | Use Case |
|-------------|---------------|----------|
| `dev` | `https://api.dev.jaak.ai` | Local development |
| `qa` | `https://api.qa.jaak.ai` | Quality assurance testing |
| `sandbox` | `https://api.sandbox.jaak.ai` | Integration testing |
| `prod` | `https://services.api.jaak.ai` | Production |

### License Error Handling

If the license is invalid, the component will display an error and not function:

```javascript
const scanner = document.getElementById('documentScanner');

scanner.addEventListener('isReady', (event) => {
  if (!event.detail) {
    console.error('Error: Invalid license or component not initialized');
  }
});
```

**Common error messages:**
- `"License key is required"` - No license provided
- `"Invalid license key"` - Invalid or expired license
- `"License validation failed"` - Validation error

## Style Guide

### CSS Custom Properties

The component supports customization through CSS custom properties:

```css
jaak-stamps {
  /* Camera view dimensions */
  --camera-width: 100%;
  --camera-height: 400px;
  
  /* Detection overlay colors */
  --detection-overlay-color: rgba(0, 123, 255, 0.3);
  --detection-border-color: #007bff;
  --detection-border-width: 2px;
  
  /* UI element colors */
  --button-background: #007bff;
  --button-text-color: #ffffff;
  --button-border-radius: 4px;
  
  /* Status indicator colors */
  --status-success-color: #28a745;
  --status-warning-color: #ffc107;
  --status-error-color: #dc3545;
}
```

### Custom CSS Classes

You can style the component container and add custom overlays:

```css
/* Component container styling */
.document-scanner-container {
  position: relative;
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  background: #000;
}

/* Custom loading overlay */
.scanner-loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 20px;
  border-radius: 8px;
  z-index: 1000;
}

/* Responsive design */
@media (max-width: 768px) {
  jaak-stamps {
    --camera-height: 300px;
  }
}
```

### Theme Support

The component automatically adapts to system dark/light mode preferences:

```css
/* Light theme (default) */
jaak-stamps {
  --background-color: #ffffff;
  --text-color: #333333;
  --border-color: #dddddd;
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
  jaak-stamps {
    --background-color: #1a1a1a;
    --text-color: #ffffff;
    --border-color: #444444;
  }
}
```

## Compatibility

### Browser Support

- **Chrome/Edge**: 80+
- **Firefox**: 75+ 
- **Safari**: 13+
- **iOS Safari**: 13+
- **Chrome Android**: 80+

### Framework Compatibility

- **Vanilla JavaScript**: Full support
- **React**: 16.8+
- **Angular**: 12+
- **Vue.js**: 3+
- **Svelte**: 3+

### Required Dependencies

- **Modern browser** with WebRTC support
- **Camera access** permission
- **HTTPS context** (required for camera access)

### Optional Dependencies

- **Web Workers** support for enhanced performance
- **WebAssembly** support for AI model acceleration

## Troubleshooting

### Common Issues

#### Camera Not Working

**Problem**: Camera preview is black or not showing

**Solutions**:
```javascript
// Check camera permissions
const scanner = document.querySelector('jaak-stamps');
const cameraInfo = await scanner.getCameraInfo();
console.log('Available cameras:', cameraInfo.availableCameras);

// Try switching cameras
await scanner.setPreferredCamera('back');
```

#### Slow Performance

**Problem**: Component is slow or laggy

**Solutions**:
```javascript
// Preload the AI model
const scanner = document.querySelector('jaak-stamps');
await scanner.preloadModel();

// Reduce capture delay for faster response
await scanner.setCaptureDelay(1000);

// Disable debug mode in production
scanner.setAttribute('debug', 'false');
```

#### No Images Captured

**Problem**: Capture completes but no images returned

**Solutions**:
```javascript
// Check component status
const status = await scanner.getStatus();
console.log('Component status:', status);

// Verify capture process completion
const isCompleted = await scanner.isProcessCompleted();
if (isCompleted) {
  const images = await scanner.getCapturedImages();
  console.log('Images:', images);
}
```

#### Memory Issues on Mobile

**Problem**: Component crashes on mobile devices

**Solutions**:
```html
<!-- Reduce memory usage -->
<jaak-stamps
  mask-size="60"
  crop-margin="15"
  capture-delay="2000">
</jaak-stamps>
```

### Frequently Asked Questions

**Q: Can I use this component without HTTPS?**
A: No, camera access requires HTTPS in production. Use `localhost` for development.

**Q: How do I handle multiple document types?**
A: Enable document classification:
```html
<jaak-stamps use-document-classification="true"></jaak-stamps>
```

**Q: Can I customize the detection area?**
A: Yes, use the `mask-size` property to adjust the detection frame size.

**Q: Can I disable automatic document detection?**
A: Yes, set `use-document-detector="false"` to disable AI detection and enable manual capture mode:
```html
<jaak-stamps use-document-detector="false"></jaak-stamps>
```

**Q: How do I get only the cropped images?**
A: Access the `cropped` property from the captured images:
```javascript
const images = await scanner.getCapturedImages();
const croppedFront = images.front.cropped; // Base64 string
```

**Q: Is the component accessible?**
A: Yes, the component includes ARIA labels and keyboard navigation support.

**Q: Can I use this in a Progressive Web App (PWA)?**
A: Yes, the component is fully compatible with PWAs and works offline after initial load.

---

## License

MIT © [Jaak AI](https://github.com/jaak-ai)