--- 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 ``: - "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". - `` 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 ``. --- ## 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