Files
GearBox/.planning/milestones/v2.2-phases/29-image-presentation/29-UI-SPEC.md
Jean-Luc Makiola 2853477a75
All checks were successful
CI / ci (push) Successful in 1m15s
CI / e2e (push) Has been skipped
CI / deploy (push) Has been skipped
chore: archive v2.2 User Experience Polish milestone
Phases 28-31 archived to milestones/v2.2-phases/
Requirements and roadmap snapshots archived to milestones/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:00:35 +02:00

7.8 KiB

phase, slug, status, shadcn_initialized, preset, created
phase slug status shadcn_initialized preset created
29 image-presentation draft false none 2026-04-12

Phase 29 — UI Design Contract

Visual and interaction contract for image presentation changes. Generated by gsd-ui-researcher, verified by gsd-ui-checker.


Design System

Property Value
Tool none (Tailwind CSS v4 direct)
Preset not applicable
Component library none (custom components)
Icon library Lucide (via custom LucideIcon wrapper)
Font System default (inherited)

Spacing Scale

Declared values (must be multiples of 4):

Token Value Usage
xs 4px Icon gaps, inline padding
sm 8px Compact element spacing
md 16px Default element spacing
lg 24px Section padding
xl 32px Layout gaps
2xl 48px Major section breaks
3xl 64px Page-level spacing

Exceptions: none


Typography

No new typography introduced. All text elements use existing typographic scale from the app.

Role Size Weight Line Height
Body 14px 400 1.5
Label 12px 500 1.25
Heading 14px 600 1.25

Color

No new brand colors introduced. The only new color element is the dominant color background which is dynamically extracted per-image.

Role Value Usage
Dominant (60%) white (#ffffff) Page background (unchanged)
Secondary (30%) gray-50 (#f9fafb) Card surfaces, fallback image bg
Accent (10%) blue-50/green-50 Weight/price badges (unchanged)
Dynamic fill per-image dominant color Image container background behind object-contain images
Fallback fill gray-100 (#f3f4f6) Image container background when dominant color unavailable

Accent reserved for: weight badges (blue-50), price badges (green-50), category badges (gray-50)


Image Container Specifications

GearImage Component

A new shared component replaces all inline <img> elements for gear/product images.

Property Spec
Component name GearImage
File location src/client/components/GearImage.tsx
Default aspect ratio 4/3 (cards, upload preview)
Detail page ratio 16/9 (global item detail, candidate detail)
Border radius rounded-xl (12px) on detail pages; inherited from parent on cards
Overflow hidden (always)

Default State (no crop)

Container: aspect-[4/3], overflow-hidden
Background: dominant color OR #f3f4f6 (gray-100 fallback)
Image: object-contain, w-full, h-full
Result: Full image visible, letterbox/pillarbox fill with dominant color

Cropped State (user-defined zoom+pan)

Container: aspect-[4/3], overflow-hidden
Background: dominant color OR #f3f4f6
Image: w-full, h-full, object-cover
Transform: scale(cropZoom) translate(cropX%, cropY%)
Transform origin: center center
Result: User-framed view with cropped overflow hidden

Empty State (no image)

Container: aspect-[4/3], bg-gray-50
Content: Centered LucideIcon (category icon), text-gray-400, size 36px

Unchanged from current behavior.

Transition

No CSS transitions on the image itself. Background color applies immediately via inline style={{ backgroundColor }}.


Zoom+Pan Editor Specifications

Editor Trigger Points

Location Trigger Behavior
ImageUpload component After image upload completes Editor overlay appears on the uploaded image
Item detail page "Adjust framing" button below image Editor overlay replaces static image view
Global item detail page "Adjust framing" button below image Same as item detail
Candidate detail page "Adjust framing" button below image Same as item detail

Editor UI

Element Spec
Library react-easy-crop
Crop shape rect
Aspect ratio Matches container (4/3 for cards, 16/9 for detail pages where applicable)
Min zoom 1.0 (fit-within, default)
Max zoom 3.0
Background Dominant color of the image (or gray-100 fallback)
Controls Zoom slider below the crop area
Save button "Save framing" — primary action, bottom-right
Cancel button "Cancel" — secondary/ghost, bottom-left
Button spacing 8px gap between cancel and save

Editor Overlay Layout

+-------------------------------------------+
|                                           |
|          [react-easy-crop area]           |
|          (drag to pan, scroll to zoom)    |
|                                           |
+-------------------------------------------+
| [------- zoom slider -------]             |
+-------------------------------------------+
| Cancel                    Save framing    |
+-------------------------------------------+
  • Overlay uses fixed inset-0 z-50 bg-black/60 on mobile, relative inline on desktop detail pages
  • On ImageUpload: overlay within the upload container
  • On detail pages: replaces the image area inline (no modal)

Editor Output

Field Type Range Description
cropZoom number 1.0 - 3.0 Zoom level (1.0 = fit within)
cropX number -50 to 50 Horizontal pan offset (percentage)
cropY number -50 to 50 Vertical pan offset (percentage)

When zoom is 1.0 and x/y are 0: equivalent to default object-contain (no crop applied).


Copywriting Contract

Element Copy
Adjust framing button "Adjust framing"
Editor save CTA "Save framing"
Editor cancel "Cancel"
Zoom slider label "Zoom" (sr-only)
Empty image placeholder "Click to add photo" (unchanged)
Backfill progress (admin) "Processing images... {N}/{total}"

Surface-by-Surface Spec

Each surface adopts the GearImage component. All surfaces use 4/3 ratio except where noted.

# Surface File Ratio Has Editor Notes
1 ItemCard components/ItemCard.tsx 4/3 No Card only, editor on detail page
2 GlobalItemCard components/GlobalItemCard.tsx 4/3 No Card only
3 CandidateCard components/CandidateCard.tsx 4/3 No Card only
4 CandidateListItem components/CandidateListItem.tsx 4/3 No Small thumbnail
5 ImageUpload components/ImageUpload.tsx 4/3 Yes Editor after upload
6 ComparisonTable components/ComparisonTable.tsx 4/3 No Table cell image
7 LinkToGlobalItem components/LinkToGlobalItem.tsx 1/1 No Small 32px thumbnail, keep object-cover for tiny icons
8 CatalogSearchOverlay components/CatalogSearchOverlay.tsx 4/3 No Search result cards (2 instances)
9 Item detail routes/items/$itemId.tsx 4/3 Yes Full editor access
10 Global item detail routes/global-items/$globalItemId.tsx 16/9 Yes Full editor access
11 Global items index routes/global-items/index.tsx 4/3 No List card
12 Candidate detail routes/threads/$threadId/candidates/$candidateId.tsx 16/9 Yes Full editor access

LinkToGlobalItem Exception

The 32x32px thumbnail in LinkToGlobalItem is too small for letterbox treatment. Keep object-cover with rounded for this surface. The GearImage component should accept a cover prop to force object-cover mode for tiny thumbnails.


Registry Safety

Registry Blocks Used Safety Gate
npm (react-easy-crop) react-easy-crop MIT license, 500k+ weekly downloads, active maintenance

No shadcn blocks used in this phase.


Checker Sign-Off

  • Dimension 1 Copywriting: PASS
  • Dimension 2 Visuals: PASS
  • Dimension 3 Color: PASS
  • Dimension 4 Typography: PASS
  • Dimension 5 Spacing: PASS
  • Dimension 6 Registry Safety: PASS

Approval: approved 2026-04-12