---
phase: 42
slug: wallpapers-social-format-conversion-voice
status: draft
shadcn_initialized: true
preset: new-york / neutral / cssVariables / lucide
created: 2026-04-04
---

# Phase 42 — UI Design Contract

> Visual and interaction contract for Phase 42: Wallpapers, Social, Format Conversion & Voice.
> Generated by gsd-ui-researcher. Verified by gsd-ui-checker.

---

## Design System

| Property | Value | Source |
|----------|-------|--------|
| Tool | shadcn | components.json detected |
| Style | new-york | components.json |
| Preset | neutral base color, cssVariables, radius=0 | ui/src/index.css |
| Component library | Radix UI (via shadcn) | components.json |
| Icon library | lucide-react | components.json |
| Font | System UI (inherited; no custom font loaded) | index.css |

**Existing components available (no reinstall needed):**
`avatar`, `badge`, `breadcrumb`, `button`, `card`, `checkbox`, `collapsible`, `command`, `dialog`, `dropdown-menu`, `input`, `label`, `popover`, `progress`, `scroll-area`, `select`, `separator`, `sheet`, `skeleton`, `tabs`, `textarea`, `toggle`, `tooltip`

**New shadcn components needed for Phase 42:**
- None. All required primitives were installed in Phase 41.

**Third-party/custom dependencies:**
- `VoiceMicButton`, `VoiceWaveform`, `VoiceModeToggle` — already exist in `ui/src/components/`. Reuse directly; do not rebuild.

---

## Spacing Scale

Declared values (multiples of 4). Inherited from Phase 41; no changes.

| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Icon gaps, badge padding, inline chip gaps |
| sm | 8px | Compact element spacing, button icon gap |
| md | 16px | Default card padding, form field spacing |
| lg | 24px | Section padding, panel gaps |
| xl | 32px | Layout column gaps, page section breaks |
| 2xl | 48px | Major section breaks |
| 3xl | 64px | Not used in Phase 42 |

**Exceptions (Phase 42 specific):**
- Drag-drop upload zone: minimum height 120px, padding 24px, dashed border `2px dashed var(--border)`.
- Format selector chips/badges: 8px horizontal padding, 4px vertical padding, `rounded-full`.
- Platform dimension labels (e.g. "2560 × 1440"): display inline in `text-xs font-mono text-muted-foreground`, right-aligned in the option row.
- VoiceMicButton: 32×32px (h-8 w-8) — existing contract from Phase 37; no change.

---

## Typography

Source: Phase 41 contract. No additions needed.

| Role | Size | Weight | Line Height | Usage |
|------|------|--------|-------------|-------|
| Body | 15px (0.9375rem) | 400 (regular) | 1.6 | Conversion UI descriptions, wallpaper prompt text |
| Label | 14px (0.875rem) | 400 (regular) | 1.5 | Form labels, format chips, panel section titles |
| Heading | 20px (1.25rem) | 600 (semibold) | 1.3 | Panel titles ("Generate Wallpaper", "Convert File") |
| Display | 28px (1.75rem) | 600 (semibold) | 1.2 | Not used in Phase 42 |

**Declared weights: 2 — 400 (regular) and 600 (semibold).** Identical to Phase 41.

**Monospace:** `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` at 14px, weight 400, line-height 1.6. Used for platform dimension labels, file MIME type display, and converted file name display.

---

## Color

Source: Phase 41 contract — Catppuccin Latte (light) + Catppuccin Mocha (dark). No new tokens introduced.

| Role | Light value | Dark value | Usage |
|------|-------------|------------|-------|
| Dominant (60%) | `#eff1f5` (--background) | `#1e1e2e` (--background) | Page background, wallpaper canvas background |
| Secondary (30%) | `#e6e9ef` (--card) | `#181825` (--card) | Format conversion panel card, image result card, social post preview card |
| Muted surface | `#ccd0da` (--secondary) | `#313244` (--secondary) | Drag-drop zone background, platform selector bg, AI fallback notice bg |
| Accent (10%) | `#bcc0cc` (--accent) | `#45475a` (--accent) | Hover states on non-primary interactive elements |
| Primary | `#1e66f5` (--primary) | `#89b4fa` (--primary) | See "Accent reserved for" below |
| Destructive | `#d20f39` (--destructive) | `#f38ba8` (--destructive) | MIME validation rejection error, destructive actions |
| Border | `#ccd0da` (--border) | `#313244` (--border) | Panel edges, drag-drop zone border, input outlines |
| Muted foreground | `#9ca0b0` (--muted-foreground) | `#6c7086` (--muted-foreground) | Platform dimension labels, helper text, secondary format labels |

**Accent (--primary) reserved for:**
1. Primary CTA buttons ("Generate Wallpaper", "Convert File", "Generate Post")
2. Job progress bar fill
3. Active tab indicator (ContentStudio Tabs, conversion format selector active state)
4. VoiceMicButton active/recording ring (`ring-2 ring-primary` — existing Phase 37 pattern)
5. Selected format chip highlight

**Drag-drop zone states:**
- Idle: `bg-secondary` background, `border-border` dashed border
- Dragover: `bg-accent/30` background, `border-primary` dashed border (primary color signals acceptance)
- Error (MIME rejection): `bg-destructive/10` background, `border-destructive` dashed border

---

## Component Inventory (Phase 42)

### ContentStudio Tab Extensions

Extend the existing `ContentStudio.tsx` Tabs component. Add three new tabs to the existing `<TabsList>`:
- "Wallpapers" (value: `wallpapers`)
- "Social" (value: `social`)
- "Convert" (value: `convert`)

Voice mic integration is in `ChatInput` / `ChatPanel` (not ContentStudio). Phase 42 does not add a "Voice" tab.

---

### Wallpaper & Social Image Panel

**WallpaperGeneratePanel** — Card with:
- Prompt textarea (4 rows, placeholder: "Describe the scene, mood, or concept…")
- Platform selector (Select component). Options grouped:
  - Desktop: "Desktop HD (2560 × 1440)", "Desktop FHD (1920 × 1080)", "Desktop 4K (3840 × 2160)"
  - Mobile: "Mobile Portrait (1080 × 1920)", "Mobile Landscape (1920 × 1080)"
  - Social: "OG Image (1200 × 630)", "Twitter Card (1200 × 628)", "Instagram Post (1080 × 1080)", "Instagram Banner (1080 × 566)", "LinkedIn Banner (1584 × 396)"
  - App: "App Icon (1024 × 1024)", "Favicon (32 × 32)"
- Dimension label renders inline in each Select option: `text-xs font-mono text-muted-foreground` right-aligned.
- "Generate Wallpaper" Button (primary, full-width of card).
- Progress bar below button (shadcn `progress`, primary fill) — same SSE job pattern as Phase 41.

**WallpaperPreview** — After job `ready`:
- Image renders in a constrained container (`max-h-80 object-contain`).
- Below image: "Download PNG" Button (primary) + resolution badge (text-xs, monospace, muted).
- If job type is app icon / favicon: shows a multi-size grid. Each cell: size label (32×32, 64×64, etc.) + Download link.

---

### Social Post Panel

**SocialPostPanel** — Card with:
- Prompt textarea (4 rows, placeholder: "Describe the topic or paste existing content to adapt…")
- Platform selector (Select). Options: "Twitter/X", "LinkedIn", "Instagram Caption", "Instagram Carousel"
- Character count indicator (below textarea, right-aligned): `text-xs text-muted-foreground`. Turns `text-destructive` when over platform limit.
- "Generate Post" Button (primary).

**SocialPostResult** — After job `ready`:
- Post text in a read-only card (`bg-card`, `rounded-lg`, `p-4`).
- Character count badge inline (text-xs, monospace, muted).
- "Copy Post" Button (secondary, full-width).
- Hashtag section below copy button: chips in `rounded-full` badges (`bg-muted text-muted-foreground`). Each chip is clickable to copy the hashtag individually.
- For carousel: shows a numbered list (slide 1, slide 2…) in collapsible sections (shadcn `collapsible`).

---

### Format Conversion Panel

**ConvertPanel** — Page layout (not nested in ContentStudio tabs; lives at `/convert` route with deep-link support `/convert/:sourceFormat/:targetFormat`).

Layout: two-column on desktop (source column left, target column right), single column on mobile.

**ConvertSourceZone** — Left column:
- Drag-drop zone (`min-h-[120px]`, `p-6`, dashed border). Copy: "Drop a file here or click to browse".
- `<input type="file" hidden>` triggered by zone click.
- After file selected: shows file name (`text-sm font-medium`), file size (`text-xs text-muted-foreground`), detected MIME type (`text-xs font-mono text-muted-foreground`).
- MIME validation error (magic-byte mismatch): replaces file metadata with a destructive error inline: "File extension does not match content. Got {actualMime}, expected {claimedMime}."

**ConvertTargetSelector** — Right column:
- Grouped format chips. Groups: Images, Audio/Video, Documents, Data.
- Each chip: `rounded-full px-3 py-1 text-xs font-medium`. Idle: `bg-muted text-muted-foreground`. Selected: `bg-primary text-primary-foreground`.
- Unavailable formats (direct converter not detected at startup): chip still shown and selectable — falls through to AI-bridged conversion rather than disabled.
- AI fallback indicator: when selected pair has no direct converter, a muted notice renders below chips: "No direct converter for this pair — AI bridge will be used." (`text-xs text-muted-foreground`, Info icon 14px, `bg-secondary` background, `rounded-md p-3`).

**ConvertActionBar** — Spans full width below both columns:
- "Convert File" Button (primary, disabled until source file selected + target format selected).
- Progress bar below button (same SSE job pattern).
- After ready: "Download {filename}.{ext}" Button (primary) replaces progress bar.

**Deep-link pre-selection:** When route is `/convert/png/svg`, source format chip "PNG" and target format chip "SVG" are pre-highlighted on mount. Drag-drop zone shows "Drop a PNG file here or click to browse" with source format in the copy.

---

### Voice Integration (Web Chat)

Voice UI components (`VoiceMicButton`, `VoiceWaveform`, `VoiceModeToggle`) were built in Phase 37. Phase 42 wires the local Whisper offline model to the existing `VoiceMicButton` — no new component surface.

**Integration contract (no new visual components):**
- `VoiceMicButton` already handles idle / recording / transcribing states with correct aria-labels.
- Phase 42 ensures the button renders in `ChatInput` when voice mode is `voice_input` or `full_voice`.
- Offline capability badge: if `WHISPER_MODEL=local`, a `text-xs text-muted-foreground` badge "Offline" renders inline next to the mic button (Wifi-off icon 12px, no tooltip needed — badge text is sufficient).

---

## Interaction Contracts

### Job Progress (shared pattern — identical to Phase 41)

1. User submits → Button shows spinner + "Generating…" label (disabled). No separate overlay.
2. SSE events arrive → Progress bar (`progress` component, primary fill) animates 0→100%.
3. On `ready` → progress bar fades out (200ms), result panel slides down (300ms ease-out). Button reverts to "Generate again" (secondary variant).
4. On `error` → progress bar fills destructive color, error inline: "Render failed — {detail}. Try again." Button reverts to primary (enabled).
5. Silent SSE reconnect on disconnect; no user-facing error unless render ultimately fails.

### Drag-Drop File Upload

1. User drags file over zone → zone border changes to `border-primary`, background to `bg-accent/30` (no animation, instant).
2. User drops file → zone shows file metadata row (name, size, MIME).
3. Magic-byte validation runs immediately (client sends file to `/api/convert/validate` or server rejects on job submit). If mismatch: zone border `border-destructive`, background `bg-destructive/10`, error copy inline.
4. User can replace file by clicking zone again while file is selected.

### Format Deep-Link

1. On mount, read `:sourceFormat` and `:targetFormat` from URL params.
2. Pre-select corresponding chips (case-insensitive, e.g. `png` matches "PNG" chip).
3. Update drag-drop zone copy to mention source format.
4. If format params are invalid or not found: silently ignore (no error shown), render default state.

### Social Character Count

1. Character count updates on every keystroke (no debounce — immediate feedback is important for limit enforcement).
2. When over limit: count turns `text-destructive`, CTA button remains enabled (AI may trim on generate; do not block submission).
3. Limit constants per platform: Twitter/X = 280, LinkedIn = 3000, Instagram Caption = 2200, Instagram Carousel (per slide) = 300.

### Hashtag Copy

- Clicking a hashtag chip copies the text (including `#`) to clipboard.
- Chip briefly shows a check icon (CheckCheck, 12px) for 1.5s, then reverts to original text.
- No toast. Inline feedback on the chip itself is sufficient.

---

## Copywriting Contract

| Element | Copy |
|---------|------|
| Wallpaper CTA | "Generate Wallpaper" |
| Wallpaper generating state | "Generating…" |
| Wallpaper download | "Download PNG" |
| Wallpaper empty state heading | "No image yet" |
| Wallpaper empty state body | "Describe a scene or mood and pick a platform size to generate a ready-to-use image." |
| Wallpaper render error | "Render failed — {detail}. Try again." |
| Social CTA | "Generate Post" |
| Social generating state | "Generating…" |
| Social copy CTA | "Copy Post" |
| Social copied state | "Copied!" (reverts after 2s) |
| Social hashtag copy (chip tooltip / accessible label) | "Copy hashtag" |
| Social empty state heading | "No post yet" |
| Social empty state body | "Describe your topic and choose a platform to generate a ready-to-publish post." |
| Social render error | "Generation failed — {detail}. Try again." |
| Character count within limit | "{N} / {limit}" |
| Character count over limit | "{N} / {limit} — over limit" |
| Convert drag-drop idle | "Drop a file here or click to browse" |
| Convert drag-drop with source format | "Drop a {FORMAT} file here or click to browse" |
| Convert drag-drop dragover | "Release to select" |
| Convert MIME mismatch error | "File extension does not match content. Got {actualMime}, expected {claimedMime}." |
| Convert AI fallback notice | "No direct converter for this pair — AI bridge will be used." |
| Convert CTA | "Convert File" |
| Convert converting state | "Converting…" |
| Convert download | "Download {filename}.{ext}" |
| Convert empty state heading | "No conversion yet" |
| Convert empty state body | "Upload a file and choose a target format to convert." |
| Convert render error | "Conversion failed — {detail}. Try again." |
| Voice offline badge | "Offline" |
| Voice mic idle (aria-label) | "Start voice input" |
| Voice mic recording (aria-label) | "Recording — speak now" |
| Voice mic transcribing (aria-label) | "Transcribing..." |

**Destructive actions in Phase 42:** None. No file deletions or irreversible actions in scope. MIME rejection is an error state, not a destructive confirmation.

---

## Registry Safety

| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | all blocks from Phase 41 (reused) | not required |

No new shadcn blocks needed. No third-party registries declared for Phase 42.

---

## Accessibility

- Drag-drop zone: `role="button"` with `tabIndex={0}`, `aria-label="Upload file — drop here or press Enter to browse"`. `onKeyDown` handler for Enter/Space triggers file picker.
- Format chips: `role="radio"` within a `role="radiogroup"` container. Each chip: `aria-checked={selected}`, `aria-label="{format name}"`.
- Platform selector: standard shadcn `Select` component — accessible by default.
- Progress bar: `role="progressbar"` with `aria-valuenow`, `aria-valuemin=0`, `aria-valuemax=100` (same contract as Phase 41).
- Hashtag chips: `role="button"`, `aria-label="Copy hashtag {tag}"`.
- Character count: `aria-live="polite"` on the count element so screen readers announce changes without interrupting.
- VoiceMicButton: existing aria-labels from Phase 37 are correct — do not change.
- Offline badge next to mic: `aria-label="Voice input is offline (local model)"` on the badge element.
- `prefers-reduced-motion`: disable drag-drop zone color transition, disable result panel slide animation. Keep progress bar animation (functional feedback).
- Wallpaper image result: `alt="{prompt text truncated to 100 chars}"` on `<img>`.

---

## 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:** pending
