# Image Crop Tool Design

**Date:** 2025-10-27
**Status:** Design Complete - Ready for Implementation
**Component:** SliderEditor → ImageUploadTab → Crop Tool

---

## Overview

The image crop tool allows AMSOIL dealers to upload images for hero slides and frame them to the required 16:9 aspect ratio. The tool combines fit (aspect ratio enforcement) and compose (creative framing) capabilities through an interactive modal with zoom/pan controls.

**Goals:**
- Ensure all slide images conform to 16:9 aspect ratio (hero slider requirement)
- Let dealers control image composition and framing
- Provide immediate visual feedback during cropping
- Generate optimized image blobs ready for preview and storage

---

## Architecture

### Component Structure

Three new components implement the crop tool:

**1. ImageCropModal.tsx**
- Overlay modal that wraps the crop editor
- Handles modal open/close state
- Receives: File object, callback to save cropped image
- Returns: Data URL of cropped image

**2. ImageCropEditor.tsx**
- Canvas-based crop interface with interactive controls
- Maintains: Original image, zoom level, pan offset, crop box position
- Displays: Source image with crop box overlay, live preview below
- Handles: Mouse events for corner dragging, zoom wheel, pan drag
- Validates: Crop coordinates maintain 16:9 aspect ratio

**3. cropImageCanvas() helper function**
- Pure function: takes source image, crop coordinates, zoom level
- Uses Canvas API to render and crop the image
- Returns: Blob of cropped image, ready for conversion to Data URL

---

## Data Flow

### Upload → Crop → Save

```
1. Dealer clicks "Upload Image" in ImageUploadTab
   ↓
2. File input dialog opens
   ↓
3. File selected → ImageCropModal opens with file
   ↓
4. ImageCropEditor displays original image with 16:9 crop box
   ↓
5. Dealer interacts:
   - Drags corners to resize crop box (locked to 16:9)
   - Scrolls mouse wheel to zoom image (1x to 2x)
   - Drags image to pan under crop box
   - Sees live preview below updating in real-time
   ↓
6. Clicks "Apply Crop" button
   ↓
7. cropImageCanvas() processes:
   - Reads source image at current zoom level
   - Applies pan offset
   - Crops to box coordinates
   - Generates blob
   ↓
8. Blob converted to Data URL
   ↓
9. Data URL stored in: slide.image.url
   ↓
10. Modal closes
    ↓
11. Cropped image appears in SliderPreview live preview
```

### State Management

**ImageCropModal state:**
- `sourceFile` - Original File object from upload
- `isOpen` - Modal visibility

**ImageCropEditor state:**
- `originalImage` - HTMLImageElement of source
- `zoomLevel` - Current zoom (1.0 to 2.0)
- `panOffsetX`, `panOffsetY` - Pan position in pixels
- `cropBox` - Object with { x, y, width, height } in image coordinates
- `previewBlob` - Blob for live preview

---

## UI/UX Design

### Modal Layout

```
┌─────────────────────────────────────────┐
│ Crop Your Image                      [X]│
├─────────────────────────────────────────┤
│                                         │
│  ┌─ Crop Editor ──────────────────────┐ │
│  │                                    │ │
│  │   [Zoomable Image with Crop Box]   │ │
│  │                                    │ │
│  │   ┌─ Live Preview (16:9) ────────┐│ │
│  │   │                              ││ │
│  │   │  [What will be saved]        ││ │
│  │   │                              ││ │
│  │   └──────────────────────────────┘│ │
│  │                                    │ │
│  │  Zoom: [━━━●━━] Pan: locked        │ │
│  │                                    │ │
│  └────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│         [Cancel]  [Apply Crop]          │
└─────────────────────────────────────────┘
```

### Interaction Details

**Crop Box:**
- Locked to 16:9 aspect ratio (width = height * 1.777...)
- Draggable corners expand/contract the box
- Minimum size: 200x112px (visible constraint)
- Maximum size: image bounds

**Zoom Controls:**
- Mouse wheel: scroll up to zoom in, scroll down to zoom out
- Range: 1.0x (fit entire image) to 2.0x (2x magnified)
- Zoom center: crop box center (intuitive framing)

**Pan Controls:**
- Click-and-drag the image to move it under the fixed crop box
- Pan only available when zoomed in (>1.0x)
- Bounded: image can't pan beyond visible edges

**Preview:**
- Updates in real-time as dealer interacts
- Shows exact 16:9 frame that will be saved
- Helps dealer validate composition before committing

---

## Technical Implementation

### Canvas Crop Processing

```typescript
function cropImageCanvas(
  sourceImage: HTMLImageElement,
  cropBox: { x, y, width, height },
  zoomLevel: number,
  panOffset: { x, y }
): Blob {
  // Calculate source coordinates accounting for zoom and pan
  const sourceX = (cropBox.x - panOffset.x) / zoomLevel
  const sourceY = (cropBox.y - panOffset.y) / zoomLevel
  const sourceWidth = cropBox.width / zoomLevel
  const sourceHeight = cropBox.height / zoomLevel

  // Create canvas at 16:9 resolution (e.g., 1920x1080 or 960x540)
  const canvas = document.createElement('canvas')
  canvas.width = 960
  canvas.height = 540

  const ctx = canvas.getContext('2d')
  ctx.drawImage(
    sourceImage,
    sourceX, sourceY, sourceWidth, sourceHeight, // source region
    0, 0, canvas.width, canvas.height              // destination (full canvas)
  )

  return new Promise(resolve => {
    canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.95)
  })
}
```

### Error Handling

**Upload validation:**
- Check file is image type (MIME type check)
- Check file size < 10MB
- Show error toast if invalid

**Canvas processing:**
- Try/catch around Canvas operations
- Fallback to original image if crop fails
- Log errors for debugging

**User feedback:**
- Loading spinner during crop processing (should be instant, but good UX)
- Success toast: "Image cropped and ready"
- Error toast: "Crop failed, using original image"

---

## Integration Points

### With ImageUploadTab

When dealer uploads file in `ImageUploadTab`:
1. File selected → Check if image → Open `ImageCropModal`
2. Modal handles crop flow
3. On success → Call callback with Data URL
4. `updateSlide()` stores Data URL in `slide.image.url`
5. `SliderPreview` immediately shows cropped image

### With ImageLibraryTab

No crop needed for library images (pre-cropped by AMSOIL).
However, future enhancement: Allow crop on library images too.

---

## Browser Compatibility

**Canvas API:** Supported in all modern browsers (IE11+ with polyfills)
**Mouse wheel zoom:** Standard DOM wheel event
**Touch pan/pinch:** Future enhancement (can add later with touch events)

---

## Testing Checklist

- [ ] Upload image opens crop modal
- [ ] Crop box displays at 16:9 ratio
- [ ] Dragging corners resizes box (maintains ratio)
- [ ] Mouse wheel zoom scales image 1x-2x
- [ ] Pan drag moves image smoothly
- [ ] Preview updates in real-time
- [ ] "Apply" generates blob and closes modal
- [ ] Cropped image appears in slide preview
- [ ] Error handling: invalid file shows error
- [ ] Error handling: canvas failure falls back to original
- [ ] Dealers can crop multiple images in sequence
- [ ] Crop works with various image sizes/formats (JPG, PNG, WebP)

---

## Future Enhancements

- **Touch support**: Pinch-to-zoom and drag gestures for mobile
- **Undo/reset**: Button to reset to original image bounds
- **Preset frames**: Quick-select buttons for common compositions (centered, thirds, etc.)
- **Image library crop**: Allow cropping of pre-made AMSOIL images
- **Crop history**: Save previous crops for re-use
- **HEIC support**: Native iOS image format support

---

## Files to Create/Modify

**New files:**
- `app/components/ImageCropModal.tsx`
- `app/components/ImageCropEditor.tsx`
- `app/components/ImageCropModal.module.css`
- `app/components/ImageCropEditor.module.css`
- `app/lib/cropImageCanvas.ts` (utility function)

**Modified files:**
- `app/components/ImageUploadTab.tsx` - Import and use `ImageCropModal`

---

## Success Criteria

✅ Crop tool successfully opens from image upload
✅ Dealers can frame images with intuitive zoom/pan
✅ Cropped images save correctly to slide state
✅ Live preview shows cropped result immediately
✅ No errors when processing various image formats
✅ UX is smooth and responsive (no lag during interactions)
