A web application that automatically generates polished photo collages in the browser. Upload multiple photos and Collage Craft ranks them with lightweight heuristics, then arranges them into clean editorial-style layouts.
- Multi-image upload — drag-and-drop or file picker, supports JPG, PNG, WEBP
- 3 layout styles — Magazine (editorial hero), Grid (clean uniform), Scrapbook (playful rotated)
- Heuristic photo scoring — ranks images by resolution, aspect ratio, file size, and orientation diversity
- Live preview — collage updates instantly as you change settings
- Drag-to-swap — reorder photos directly on the canvas by dragging one tile onto another
- Local persistence — uploaded photos are restored from IndexedDB after a reload on the same device
- Export — download as a high-quality 2× PNG
- Theme toggle — switch between light, dark, and system modes
- Export feedback — Sonner toasts confirm export progress and failures
- Frontend — React 19 + TypeScript + Vite
- Styling — Tailwind CSS v4 + shadcn/ui patterns
- Linting & Formatting — Biome
- Testing — Vitest
- Rendering — HTML canvas for preview and PNG export
- Node.js 20+
# Install dependencies
npm install
# Start development server
npm run devOpen http://localhost:5173 in your browser.
npm run build
npm run preview # preview the production buildnpm run lint # Biome check
npm run test # Vitest
npm run typecheck # TypeScript type check only
npm run format # Biome format
npm run check # Biome check + auto-fix- Upload photos with drag-and-drop or the file picker.
- The app reads image metadata and creates lightweight object URLs for previews.
- Photos are ranked with a simple heuristic scorer.
- The layout engine arranges them into
magazine,grid, orscrapbookcompositions. - The canvas preview updates live as you change style, size, spacing, or title.
- Export renders the collage to a high-resolution PNG and starts the download automatically.
src/
├── types/
│ └── index.ts # Shared interfaces: Photo, LayoutTile, CollageSettings, …
├── lib/
│ ├── constants.ts # Canvas presets, density gaps, default settings
│ ├── scoring.ts # Photo scoring module and ranking helpers
│ ├── layout-engine.ts # Grid / Magazine / Scrapbook layout generators
│ ├── persisted-photos.ts # IndexedDB persistence for uploaded photos
│ ├── tile-assignments.ts # Drag-to-swap photo assignment helpers
│ └── canvas-renderer.ts # Canvas 2D rendering + PNG export
├── hooks/
│ ├── use-photo-upload.ts # Object URL creation, drag-drop, Photo object creation
│ ├── use-collage.ts # Orchestrates scoring → layout → state
│ ├── use-canvas-export.ts # Triggers 2× PNG download + toast feedback
│ └── use-is-dark.ts # Resolved dark-mode boolean (handles "system" setting)
├── components/
│ ├── canvas/
│ │ ├── collage-canvas.tsx # <canvas> element + live re-render + drag-to-swap
│ │ └── empty-state.tsx # Placeholder before photos are uploaded
│ ├── controls/
│ │ ├── controls-panel.tsx # Full sidebar assembly
│ │ ├── style-selector.tsx # Magazine / Grid / Scrapbook picker
│ │ └── canvas-size-control.tsx
│ └── upload/
│ ├── upload-zone.tsx # Drag-and-drop area
│ └── photo-strip.tsx # Scrollable thumbnail row
└── App.tsx # Top-level layout and state wiring
Collage Craft uses a simple heuristic scorer to decide which photos should be featured first in a layout.
The scorer ranks each photo based on:
| Factor | Weight | Rationale |
|---|---|---|
| Resolution (pixels) | 35% | More pixels = richer image |
| Aspect ratio fit | 30% | Common ratios (16:9, 4:3, 3:2…) score higher |
| File size | 20% | Proxy for compression quality |
| Orientation diversity | 15% | Bonus for photos that diversify the mix |
These scores are then used to keep stronger photos near the front of the layout while still allowing some seeded variation in the final arrangement.
- No backend or cloud sync; everything runs locally in the browser
- Uploaded photos persist locally, but full project state and settings are not saved yet
- No manual photo pinning beyond drag-to-swap reassignment
- Scoring is intentionally simple and heuristic-based
This project is licensed under the MIT License. See LICENSE for details.
