Files
SnapOtter/docs/superpowers/specs/2026-03-23-resize-rotate-redesign.md
T
Siddharth Kumar Sah fc01a2f536 docs: address spec review feedback for resize/rotate redesign
Adds: state architecture for rotate preview, dimension data source,
fit mode defaults, mobile layout notes, extensibility guidance.
2026-03-23 15:14:34 +08:00

95 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Resize & Rotate/Flip UX Redesign
## Problem
The before/after comparison slider is a poor fit for resize and rotate/flip tools:
- **Resize** overlays two different-sized images — doesn't communicate anything useful. Users care about "how big will it be?" not pixel-level comparison.
- **Rotate/Flip** transformations are self-evident. A slider adds nothing.
The resize settings also use technical jargon (contain, cover, fill, inside, outside) that confuses layman users.
## Design
### Resize: Tab-Based Settings with Presets Front-and-Center
Replace the current mode toggle (Pixels/Percentage) with three tabs:
#### Presets Tab (default)
- Single-column scrollable list of cards grouped by platform (Instagram, Twitter/X, Facebook, YouTube, LinkedIn) — the sidebar is 18rem wide, so single-column avoids cramped cards
- Each card shows: platform icon, preset name (e.g., "Post", "Story", "Header"), dimensions (e.g., "1080 × 1080")
- Clicking a card selects it (highlighted border), clicking again deselects
- Selected preset populates the dimensions automatically
- Presets use "Crop to fit" (cover) as the default fit mode — this is the expected behavior for social media sizing
- "Don't enlarge" checkbox available below preset grid
- Process button at bottom
#### Custom Size Tab
- Width and Height number inputs
- Aspect ratio lock toggle between them (link/unlink icon)
- Fit mode as 3 plain-language options:
- "Crop to fit" (maps to sharp `cover`)
- "Fit inside" (maps to sharp `contain`)
- "Stretch" (maps to sharp `fill`)
- Remove "inside" and "outside" fit modes — they confuse laymen
- "Don't enlarge" checkbox
- Process button at bottom
#### Scale Tab
- Percentage number input
- Quick-select buttons: 25% | 50% | 75%
- `fit` and `withoutEnlargement` are intentionally omitted — percentage scaling doesn't need them. API defaults (`contain`, `false`) are sent.
- Process button at bottom
### Resize: Side-by-Side Result Display
Replace the before/after slider with side-by-side thumbnails:
- Two image thumbnails side by side, each fitted within its half
- **Left**: "Original" label, the original image, dimensions below (e.g., "3000 × 2000"), file size (e.g., "2.4 MB")
- **Right**: "Resized" label, the processed image, new dimensions below (e.g., "1080 × 720"), file size (e.g., "340 KB")
- File size savings shown between/below (e.g., "86% smaller")
- Checkerboard background for transparency (same pattern as current viewer)
- **Dimensions**: Read client-side from blob URLs using `Image.onload` to get `naturalWidth`/`naturalHeight`. No backend changes needed.
- **File sizes**: Use existing `originalSize`/`processedSize` from the API response (already available in the file store).
- **Mobile**: On small screens, thumbnails stack vertically instead of side-by-side.
- Review panel (undo, download, continue editing) remains unchanged
### Rotate/Flip: Live CSS Preview
Replace the "process then compare" flow with live preview:
**State architecture**: `rotate-settings.tsx` emits transform values (angle, flipH, flipV) via a callback prop from `tool-page.tsx`. `tool-page.tsx` holds the preview transform state and passes it down to `ImageViewer` as optional props (`cssRotate`, `cssFlipH`, `cssFlipV`). `ImageViewer` applies these as CSS `transform: rotate(Xdeg) scaleX(Y) scaleY(Z)`.
- When a file is loaded, it shows in the image viewer as normal
- As the user adjusts controls (rotate buttons, angle slider, flip toggles), CSS transforms update the preview in real-time — no server call
- Controls stay the same: quick rotate 90 left/right, angle slider 0-360, horizontal/vertical flip toggles
- **Non-90-degree angles**: CSS preview will clip corners (the image rotates within its container). This is acceptable as a preview — the final server output will have proper canvas extension. This is a known discrepancy.
- The "Process" button label changes to "Apply" to signal finality
- "Apply" button remains disabled when no changes are made (angle=0, no flips) — same as current behavior
- Clicking "Apply" sends to the server, produces the final file
### Rotate/Flip: Result Display
After applying:
- Result shows in the standard ImageViewer (no before/after slider, no side-by-side)
- The transformation is self-evident
- Review panel appears with undo/download options
### Other Tools
The `BeforeAfterSlider` remains for all other tools (compress, filters, etc.). Only resize and rotate/flip get special treatment. In `tool-page.tsx`, branch on `toolId` using a set (e.g., `TOOLS_WITHOUT_SLIDER`) for extensibility.
## Files to Modify
### Frontend
- `apps/web/src/components/tools/resize-settings.tsx` — rewrite with tab-based UI
- `apps/web/src/components/tools/rotate-settings.tsx` — emit transform values via callback, change button label to "Apply"
- `apps/web/src/pages/tool-page.tsx` — hold preview transform state, conditionally render side-by-side for resize, ImageViewer for rotate/flip, BeforeAfterSlider for everything else
- `apps/web/src/components/common/image-viewer.tsx` — accept optional CSS transform props for live rotate/flip preview
- `apps/web/src/components/common/side-by-side-comparison.tsx` — new component for side-by-side thumbnail comparison with dimensions and file size
### No Backend Changes
- Resize and rotate API routes remain unchanged
- Image engine operations remain unchanged
- Only the frontend presentation and interaction model changes