docs(phase-2): UI design contract for Content Core

Adds 02-UI-SPEC.md covering photo posts, stories, milestone posts,
social graph, home feed, and safety (block/report) — extends Phase 1
contract without contradiction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Sonnet 4.6 committed 2026-06-06 11:56:23 +02:00
1 parent eeab28e8cb
commit 0f7b00283f
1 file changed
+602
@@ -0,0 +1,602 @@
---
phase: 2
slug: content-core
status: draft
shadcn_initialized: true
preset: base-nova
created: 2026-06-06
---
# Phase 2 — UI Design Contract
> Visual and interaction contract for Phase 2: Content Core. Extends Phase 1 contract without contradiction. Covers all screens introduced by PHOTO-01–PHOTO-04, STORY-01–STORY-03, MILE-01–MILE-02, SOCL-01–SOCL-03, FEED-01, SAFE-01–SAFE-02.
Source of Phase 1 tokens: `.planning/phases/01-foundation/01-UI-SPEC.md` (LOCKED — not modified here).
---
## Design System
| Property | Value | Source |
|----------|-------|--------|
| Tool | shadcn/ui | Phase 1 — locked |
| Preset | `base-nova` (neutral base, orange-500 primary) | Phase 1 SUMMARY.md |
| Component library | Radix UI (via shadcn) | Phase 1 — locked |
| Icon library | Lucide React | Phase 1 — locked |
| Font | Inter — loaded via `next/font/google` | Phase 1 — locked |
| Style | `base-nova` (no `asChild` on DropdownMenu) | Phase 1 SUMMARY.md |
### shadcn Status
`components.json` exists. Style: `base-nova`. Orange primary confirmed in `globals.css` (`--primary: oklch(0.7024 0.1967 41.45)`, `--ring` matching). No re-initialisation required.
### New Components Required in Phase 2
Additions to the Phase 1 component set (official shadcn registry only):
| Component | Used For | shadcn Add Command |
|-----------|----------|--------------------|
| `tabs` | Profile grid tab bar (Posts / Milestones), story viewer tabs | `npx shadcn@latest add tabs` |
| `carousel` | Photo carousel post (up to 10 images) | `npx shadcn@latest add carousel` |
| `sheet` | Story viewer full-screen overlay (mobile), follower/following slide-up panel | `npx shadcn@latest add sheet` |
| `alert-dialog` | Block confirmation, report confirmation, delete post confirmation, unfollow confirmation | `npx shadcn@latest add alert-dialog` |
| `scroll-area` | Home feed infinite scroll container, follower/following list | `npx shadcn@latest add scroll-area` |
| `popover` | Milestone type picker, post options menu | `npx shadcn@latest add popover` |
| `radio-group` | Report reason selector | `npx shadcn@latest add radio-group` |
All components sourced from shadcn official registry only. No third-party blocks.
---
## Spacing Scale
Unchanged from Phase 1. All values are multiples of 4px.
| Token | Value | Tailwind Class | Usage |
|-------|-------|----------------|-------|
| xs | 4px | `gap-1` / `p-1` | Icon gaps, story ring gap, reaction icon padding |
| sm | 8px | `gap-2` / `p-2` | Post metadata spacing, caption line gap |
| md | 16px | `gap-4` / `p-4` | Card inner padding, feed item spacing, form fields |
| lg | 24px | `gap-6` / `p-6` | Section padding, feed container horizontal padding |
| xl | 32px | `gap-8` / `p-8` | Feed item vertical gap, profile stats row spacing |
| 2xl | 48px | `gap-12` / `p-12` | Feed top/bottom breathing room |
| 3xl | 64px | `gap-16` / `p-16` | Page-level top/bottom spacing |
**Phase 2 exceptions:**
| Exception | Value | Reason |
|-----------|-------|--------|
| Minimum touch target (follow, unfollow, block, report buttons) | 44px height | WCAG 2.5.5 minimum touch target |
| Story ring gap between ring and avatar edge | 3px | Visual design — thin white offset for ring visibility |
| Carousel dot indicator spacing | 6px gaps | Not a multiple of 4 — approved exception for dot alignment only |
---
## Typography
Unchanged from Phase 1. Exactly 4 sizes, 2 weights.
| Role | Size | Weight | Line Height | Tailwind | Usage in Phase 2 |
|------|------|--------|-------------|----------|-----------------|
| Page heading | 20px | 600 | 1.2 | `text-xl font-semibold` | Feed section heading, milestone title, follower list page heading |
| Section heading | 16px | 600 | 1.5 | `text-base font-semibold` | Post pet name, milestone type label, story pet name overlay |
| Body / Label / Button | 14px | 400 | 1.6 | `text-sm` | Post caption, feed metadata, follower counts, form labels, buttons |
| Caption | 12px | 400 | 1.5 | `text-xs` | Post timestamp, view count, "managed by @owner", carousel index "2 / 10" |
**Phase 2 type rules:**
- Post pet name (above feed card): Section heading (`text-base font-semibold`)
- Post caption: Body (`text-sm`) — weight 400; up to 3 lines collapsed, "more" expand link
- Post timestamp: Caption (`text-xs`) — muted foreground
- Story viewer pet name overlay: Section heading (`text-base font-semibold`) — white text on darkened overlay
- Milestone post type label ("Birthday", "Adoption Anniversary"): Section heading (`text-base font-semibold`) — accent-colored
- Follower/following count on profile: Body (`text-sm`) with count in Page heading (`text-xl font-semibold`) above it
- Feed empty state heading: Page heading (`text-xl font-semibold`)
- Report modal copy: Body (`text-sm`)
---
## Color
Unchanged from Phase 1. Light mode only.
| Role | Value | Tailwind | Usage |
|------|-------|----------|-------|
| Dominant (60%) | `#FAFAFA` / white | `bg-background` | Page background, feed background, story viewer backdrop darkened overlay |
| Secondary (30%) | `#F4F4F5` | `bg-muted` / `bg-card` | Post card background, skeleton loaders, profile stats card |
| Accent (10%) | `#F97316` | `orange-500` | See accent reservation list below |
| Foreground | `#09090B` | `text-foreground` | Post caption, all body text |
| Muted foreground | `#71717A` | `text-muted-foreground` | Post timestamp, "managed by @owner", secondary metadata |
| Border | `#E4E4E7` | `border` | Post card border, carousel slide border |
| Destructive | `#EF4444` | `text-destructive` / `red-500` | Block confirmation, report submission, delete post action |
| Ring / Focus | `#F97316` | `ring-orange-500` | Focus ring on all interactive elements |
| Story ring (active) | `#F97316` | `ring-orange-500` | 3px ring with 2px white offset on avatar with active stories |
| Milestone surface | `#FFF7ED` | `bg-orange-50` | Milestone post card background — warm tint to distinguish from photo posts |
### Accent Reservation — `#F97316` (orange-500) — Phase 2 additions
Inherited from Phase 1 (locked):
1. Primary CTA buttons (`bg-orange-500 hover:bg-orange-600 text-white`)
2. **Story ring on pet avatar when active stories exist** — activated in Phase 2
3. Paw-print reaction icon active state (Phase 3 — reserved)
4. Active nav item indicator
5. Upload progress bar fill
6. Focus ring on all interactive elements
Phase 2 additions to the reservation list:
7. Follow button — primary CTA style (`bg-orange-500`) — until followed; switches to outline on follow
8. Milestone type label text (`text-orange-500 font-semibold`) — the milestone type label only ("Birthday"), not the surrounding card
9. Story progress bar at top of story viewer (thin 3px bar fill while story is visible)
Accent is NOT used for: carousel dots (use `bg-muted` inactive / `bg-foreground` active), post metadata, like counts, follower counts, or any decorative surface.
---
## Screen Inventory — Phase 2
All screens introduced in PHOTO-01–PHOTO-04, STORY-01–STORY-03, MILE-01–MILE-02, SOCL-01–SOCL-03, FEED-01, SAFE-01–SAFE-02.
### Home Feed
| Screen | Route | Key Components | Focal Point |
|--------|-------|----------------|-------------|
| Home feed | `/` (app root after auth) | `ScrollArea`, `PostCard`, `SkeletonCard`, cursor pagination trigger | Chronological list of `PostCard` items; infinite scroll loads next page on bottom intersection |
| Empty feed (zero follows) | `/` | `EmptyFeed` state illustration placeholder | "No posts yet" centered state with "Find pets to follow" CTA button |
**Feed layout:** Single-column, max-width `640px`, centered, horizontal padding `lg` (24px). Each `PostCard` is separated by `xl` (32px) vertical gap.
### Post Creation
| Screen | Route | Key Components | Focal Point |
|--------|-------|----------------|-------------|
| New post (photo) | `/posts/new` | Multi-image upload zone, caption `Textarea`, CTA "Share Post" | Upload zone at top; caption below; orange "Share Post" CTA at bottom |
| Post created (success) | Redirect to `/` or pet profile | `Toast` success | Toast: "Post shared" |
### Photo Post Card (Feed Item)
Not a route — a reusable component rendered in the feed and on the pet profile grid.
| State | Visual Treatment |
|-------|-----------------|
| Single image | Image fills card width at natural aspect ratio (capped at 4:3 portrait, 16:9 landscape) |
| Carousel (2–10) | First image shown; left/right chevron buttons (Lucide `ChevronLeft` / `ChevronRight`); dot indicators below; index caption "1 / 5" at top-right in Caption style |
| Caption collapsed | 3 lines max, "more" expand link in muted foreground |
| Caption expanded | Full caption, "less" collapse link |
| Loading | `Skeleton` — 80px header row (avatar + name) + image aspect placeholder + 2 skeleton text lines |
### Stories
| Screen | Route | Key Components | Focal Point |
|--------|-------|----------------|-------------|
| Story ring display | Inline on avatar (feed, pet profile) | Modified `Avatar` with orange ring | 3px `ring-orange-500` with 2px white offset; ring present only when `hasActiveStory === true` |
| Story tray (home feed top) | Top of `/` feed | Horizontally scrollable row of `Avatar` with pet name below | First item is "Add story" (`+` icon in orange circle); remaining are followed pets with active stories |
| Story viewer | `/stories/[storyId]` or modal overlay | `Sheet` (full-screen on mobile), image fill, pet name overlay, progress bar, viewer list icon | Full-screen image; progress bar top (orange, 3px); pet name + avatar overlay bottom-left; close `X` top-right |
| Story upload | `/stories/new` | Single image upload zone (same as `AvatarUpload` pattern), CTA "Post Story" | Upload zone full-width; orange "Post Story" CTA |
| Story viewers list | Sheet panel from story viewer | Scrollable list of `Avatar` + pet name items | "Seen by {N}" heading; list of viewer pet avatars + names |
### Milestone Posts
| Screen | Route | Key Components | Focal Point |
|--------|-------|----------------|-------------|
| New milestone | `/milestones/new` | `Popover` milestone type picker, optional caption, optional photo, CTA "Share Milestone" | Milestone type picker is the primary interaction; orange "Share Milestone" CTA |
| Milestone card (feed / profile) | Inline component | `Card` with `bg-orange-50` surface, milestone icon (Lucide), type label in orange | Orange-50 background distinguishes milestone from standard photo post at a glance |
**Milestone type visual mapping:**
| Milestone Type | Lucide Icon | Label |
|----------------|-------------|-------|
| Birthday | `Cake` | "Birthday" |
| Adoption Anniversary | `Heart` | "Adoption Anniversary" |
| First Walk | `Footprints` | "First Walk" |
| First Vet Visit | `Stethoscope` | "First Vet Visit" |
| Custom | `Star` | User-defined label (max 40 chars) |
### Social Graph (Follow / Followers / Following)
| Screen | Route | Key Components | Focal Point |
|--------|-------|----------------|-------------|
| Pet profile — public | `/pets/[petId]` | Avatar, name, species badge, stats row (posts / followers / following), post grid, Follow/Unfollow button | Follow button top-right of stats row; orange when unfollowed, outline when following |
| Followers list | `/pets/[petId]/followers` | `ScrollArea` + list of `PetListItem` rows | Each row: avatar (sm) + pet name + Follow/Unfollow button; "X followers" heading |
| Following list | `/pets/[petId]/following` | `ScrollArea` + list of `PetListItem` rows | "Following X pets" heading; each row same as followers |
**Follow button states:**
| State | Label | Style |
|-------|-------|-------|
| Not following | "Follow" | `bg-orange-500 text-white` (primary) |
| Following | "Following" | `variant="outline"` with checkmark icon |
| Pending (optimistic) | Disabled spinner | `opacity-60 cursor-not-allowed` |
| Own pet | Hidden | Follow button not rendered on own pet profile |
### Safety (Block / Report)
| Screen | Route | Key Components | Focal Point |
|--------|-------|----------------|-------------|
| Block pet confirmation | `AlertDialog` (modal, no separate route) | Heading, body, "Block" (destructive), "Cancel" | `AlertDialog` triggered from pet profile options menu |
| Report post / pet | `Sheet` (slide-up panel) | Radio group of report reasons, optional text, "Submit Report" CTA | Reason selector is the primary interaction; "Submit Report" is destructive-styled |
| Block success feedback | `Toast` | — | "You've blocked {pet name}" |
| Report success feedback | `Toast` | — | "Report submitted. We'll review it shortly." |
**Post options menu:** Three-dot `MoreHorizontal` Lucide icon button in top-right of each `PostCard`. Opens `DropdownMenu` with:
- Own post: "Edit caption", "Delete post"
- Other's post: "Report post", separator, "Block {pet name}"
---
## Component Specifications
### PostCard
```
Container: bg-card border border-border rounded-lg overflow-hidden
Max-width: 640px (inherited from content area)
Padding: 0 (image bleeds to card edges); header/footer padded md (16px)
Header row (16px padding horizontal, 12px vertical):
- Avatar sm (32px) with story ring if applicable
- Pet name: text-base font-semibold (16px/600)
- Timestamp: text-xs text-muted-foreground (12px/400)
- MoreHorizontal icon button: top-right, 44px touch target
Image area:
- Width: 100% of card
- Aspect ratio: image natural, capped at max-height 560px; object-fit: cover
- Carousel: Embla-backed shadcn Carousel; dot indicators below image
Footer (16px padding):
- Caption: text-sm (14px/400), 3-line clamp with expand link
- Post metadata: likes count (Phase 3), comment count (Phase 3) — reserve space, render 0 for now
```
### Story Ring
Applied to the `Avatar` component by wrapping with an orange ring:
```css
/* Applied conditionally when hasActiveStory === true */
ring-[3px] ring-orange-500 ring-offset-[2px] ring-offset-background
```
Unseen stories: `ring-orange-500`
All stories seen: `ring-muted-foreground` (gray ring — indicates story exists but is already viewed)
No active story: no ring class applied
### Story Tray (Feed Top)
```
Layout: horizontal flex row, overflow-x-auto, gap-md (16px), px-lg (24px), py-sm (8px)
Height: 96px (avatar 64px + name 12px caption + 8px gap + 12px padding)
Avatar size: 64px (custom size between md and lg — exception to the 4 standard sizes; applied via w-16 h-16)
Pet name: text-xs text-muted-foreground, center-aligned below avatar, max-width 64px, truncate
First item "Add story": orange-500 circle with white + icon; label "Add story" in text-xs
```
### Milestone Card
```
Container: bg-orange-50 border border-orange-200 rounded-lg p-md (16px)
Icon: Lucide icon at 32px, text-orange-500
Milestone type label: text-base font-semibold text-orange-500
Pet name + date: text-sm text-muted-foreground below type label
Caption (if present): text-sm mt-sm (8px)
Photo (if present): rounded-md below caption, full card width
```
### PetListItem (Followers / Following)
```
Layout: flex row, items-center, gap-sm (8px), px-md (16px), py-sm (8px)
Avatar: sm (32px)
Pet name: text-sm font-semibold
Species badge: shadcn Badge variant="secondary" text-xs
Follow button: right-aligned, 44px min-height, primary or outline state per follow status
```
### Story Viewer
```
Backdrop: fixed inset-0 bg-black/80
Image: object-fit contain, centered, max-height 100dvh
Progress bar: absolute top-0 left-0 right-0 h-[3px] bg-muted
Fill: bg-orange-500 transitioning from 0% to 100% over 5s (CSS transition)
Pet info overlay: absolute bottom-md (16px) left-md (16px)
Avatar sm (32px) + pet name text-sm font-semibold text-white
Close button: absolute top-md (16px) right-md (16px), X icon, text-white, 44px touch target
Viewer list button: absolute top-md (16px) left-md (16px), eye icon + count, text-white
```
### Report Sheet
```
Sheet side: bottom (mobile) / right (desktop)
Sheet title: "Report" (text-xl font-semibold)
RadioGroup: 5 options stacked, gap-sm (8px) between each
Options: "Spam", "Harassment", "Inappropriate content", "Misinformation", "Other"
Optional notes Textarea: placeholder "Add details (optional)", max 500 chars
Primary CTA: "Submit Report" — destructive styling (bg-destructive text-white)
Cancel: text link "Cancel" in muted foreground
```
---
## Interaction Contracts
### Infinite Scroll (FEED-01)
- Use `IntersectionObserver` on a sentinel element at the bottom of the feed list.
- When sentinel enters viewport: call `fetchNextPage()` from TanStack Query `useInfiniteQuery`.
- Show `SkeletonCard` (3 items) while loading the next page.
- Cursor is the `createdAt` timestamp of the last visible post (ISO string).
- End of feed state: "You're all caught up. Follow more pets to see more posts." — centered, Caption style.
### Optimistic Follow/Unfollow (SOCL-01, SOCL-02)
- On button press: immediately toggle button state (optimistic).
- On server error: revert to previous state + toast "Something went wrong. Try again."
- Follow count on profile stats row updates optimistically (+1 / -1).
### Story Expiry Display
- Stories older than 24h are not fetched (backend filters by `expires_at`).
- Story ring disappears automatically when the last story expires — no client polling; resolved on next page load.
- Story viewer auto-advances: 5 seconds per image, then closes.
### Caption Expand/Collapse
- Default: 3-line clamp via `line-clamp-3` Tailwind class.
- "more" link: `text-xs text-muted-foreground` appended inline; click removes clamp.
- "less" link: same style, returns to 3-line clamp.
- Transition: none (instant toggle — no animation to avoid layout shift in feed).
### Post Options Menu
- Trigger: `MoreHorizontal` Lucide icon button; `aria-label="Post options"`.
- Own post: shows "Edit caption" (opens inline edit mode) and "Delete post" (opens `AlertDialog`).
- Other's post: shows "Report post" (opens `Sheet`) and "Block {pet name}" (opens `AlertDialog`).
- Edit caption: inline `Textarea` replaces caption text; "Save" / "Cancel" inline buttons appear.
---
## Copywriting Contract
All copy for Phase 2 screens. Locked.
### Photo Post Copy
| Element | Copy |
|---------|------|
| New post page heading | "New Post" |
| Upload zone idle label | "Add photos" |
| Upload zone sub-label | "Drag photos here or tap to browse — up to 10" |
| Caption placeholder | "Write a caption…" |
| Primary CTA | **"Share Post"** |
| Post shared toast | "Post shared" |
| Caption edit CTA | "Save caption" |
| Caption edit cancel | "Cancel" |
| Caption edited toast | "Caption updated" |
| Delete post menu item | "Delete post" |
| Delete post dialog heading | "Delete this post?" |
| Delete post dialog body | "This will permanently delete the post and all its photos. This can't be undone." |
| Delete post confirm button | "Yes, delete" |
| Delete post cancel button | "Keep post" |
| Post deleted toast | "Post deleted" |
### Story Copy
| Element | Copy |
|---------|------|
| Story upload page heading | "New Story" |
| Upload zone idle label | "Add a photo" |
| Upload zone sub-label | "Stories disappear after 24 hours" |
| Primary CTA | **"Post Story"** |
| Story posted toast | "Story posted" |
| Story tray add item label | "Add story" |
| Story viewer viewers button aria-label | "Seen by {N} pets" |
| Story viewers list heading | "Seen by {N}" |
| Story viewers empty state | "No views yet" |
| Story expired notice (edge case) | "This story has expired." |
### Milestone Copy
| Element | Copy |
|---------|------|
| New milestone page heading | "New Milestone" |
| Milestone type picker label | "What's the milestone?" |
| Caption placeholder | "Add a note… (optional)" |
| Primary CTA | **"Share Milestone"** |
| Milestone shared toast | "Milestone shared" |
### Social Graph Copy
| Element | Copy |
|---------|------|
| Follow button label (not following) | "Follow" |
| Follow button label (following) | "Following" |
| Unfollow confirmation dialog heading | "Unfollow {pet name}?" |
| Unfollow confirmation dialog body | "You'll stop seeing {pet name}'s posts in your feed." |
| Unfollow confirm button | "Unfollow" |
| Unfollow cancel button | "Keep following" |
| Followers list page heading | "{N} Followers" |
| Following list page heading | "Following {N}" |
| Follow toast | "You're now following {pet name}" |
| Unfollow toast | "Unfollowed {pet name}" |
| Followers empty state | "{pet name} doesn't have any followers yet." |
| Following empty state | "{pet name} isn't following anyone yet." |
### Home Feed Copy
| Element | Copy |
|---------|------|
| Feed page title (document) | "Home — PawFeed" |
| Feed empty state heading | "Your feed is empty" |
| Feed empty state body | "Follow some pets to see their posts here." |
| Feed empty state CTA | **"Find Pets"** |
| End of feed message | "You're all caught up. Follow more pets to see more posts." |
| Loading more indicator | "Loading more posts…" (sr-only, visually show skeleton only) |
### Safety Copy
| Element | Copy |
|---------|------|
| Post options button aria-label | "Post options" |
| Report post menu item | "Report post" |
| Block pet menu item | "Block {pet name}" |
| Report sheet heading | "Report" |
| Report reason label | "Why are you reporting this?" |
| Report reason: spam | "Spam" |
| Report reason: harassment | "Harassment or bullying" |
| Report reason: inappropriate | "Inappropriate content" |
| Report reason: misinformation | "False or misleading information" |
| Report reason: other | "Something else" |
| Report notes placeholder | "Add details (optional)" |
| Submit report CTA | **"Submit Report"** |
| Report cancel link | "Cancel" |
| Report submitted toast | "Report submitted. We'll review it shortly." |
| Block confirmation dialog heading | "Block {pet name}?" |
| Block confirmation dialog body | "{pet name}'s posts and profile will be hidden from you. They won't know you blocked them." |
| Block confirm button | "Block" |
| Block cancel button | "Cancel" |
| Block success toast | "You've blocked {pet name}" |
### Error States
| Trigger | Message |
|---------|---------|
| Post upload: file too large | "Photo must be under 10 MB." |
| Post upload: wrong format | "Upload a JPG, PNG, or WebP image." |
| Post upload: too many images | "You can upload up to 10 photos per post." |
| Post save error | "Couldn't share your post. Please try again." |
| Story upload error | "Couldn't post your story. Please try again." |
| Milestone save error | "Couldn't share this milestone. Please try again." |
| Follow error | "Something went wrong. Try again." |
| Report submit error | "Couldn't submit your report. Please try again." |
| Feed load error | "Couldn't load posts. Pull to refresh or try again." |
| Caption too long (500 char limit) | "Caption must be 500 characters or fewer." |
---
## Post Profile Grid (Pet Profile Page — Phase 2 additions)
The pet profile page established in Phase 1 gains a post grid and stats row in Phase 2.
### Stats Row
```
Layout: flex row, gap-xl (32px), centered
Items: "N posts", "N followers", "N following"
- Count: text-xl font-semibold (20px/600)
- Label: text-xs text-muted-foreground (12px/400) below count
- "followers" and "following" are tappable links to list pages
```
### Post Grid
```
Layout: CSS grid, 3 columns, gap-xs (4px)
Each cell: square aspect ratio, object-fit: cover
Carousel post: first image shown with multi-image icon (Lucide Layers2) overlay top-right
Milestone post: bg-orange-50 cell with milestone icon centered (no thumbnail image)
On tap: navigate to individual post view (Phase 2 does not define a post detail route — post detail is Phase 3; tapping post in grid has no action in Phase 2)
```
### Profile Tabs (Phase 2 addition)
```
Component: shadcn Tabs
Tabs: "Posts" | "Milestones"
- Posts tab: 3-column grid of all photo posts
- Milestones tab: chronological list of MilestoneCard components
Default active: "Posts"
Tab indicator: orange-500 underline on active tab (shadcn default data-[state=active] style)
```
---
## Animation and Motion
Minimal — extends Phase 1 contract.
| Element | Behavior |
|---------|---------|
| Feed skeleton load | `animate-pulse` on `SkeletonCard` |
| Story progress bar | CSS `transition-all duration-[5000ms] linear` on width |
| Story viewer open | `Sheet` default Radix animation (~150ms fade+slide) |
| Carousel slide | Embla default (touch-native swipe, CSS transform) |
| Follow button state change | `transition-colors duration-150` |
| Caption expand/collapse | None — instant to avoid feed layout shift |
| AlertDialog open | Radix default fade+scale (~150ms) |
| Post options DropdownMenu | shadcn default (~150ms fade+slide) |
No `framer-motion` in Phase 2. CSS transitions and Radix built-in animations only.
---
## Layout Extensions (Phase 2)
Extends the Phase 1 app shell without contradiction.
| Layout Area | Phase 1 Value | Phase 2 Addition |
|-------------|--------------|-----------------|
| Content area max-width | 640px | Unchanged |
| Feed container | Not defined in Phase 1 | `max-w-[640px] mx-auto px-6 py-8` |
| Story tray | Not defined in Phase 1 | `w-full overflow-x-auto px-6 py-2` above feed list |
| Story viewer | Not defined in Phase 1 | `fixed inset-0 z-50` (above sidebar and nav) |
| Post grid (profile) | Not defined in Phase 1 | 3-column `grid gap-1` within `max-w-[640px]` content area |
| Followers/following list | Not defined in Phase 1 | `max-w-[480px] mx-auto` |
**Mobile bottom nav — Phase 2 addition:**
The center "Post" tab in the mobile bottom nav (defined in Phase 1 as accent-filled) gains a Sheet-based post type picker:
- Tap "+" center button opens bottom `Sheet` with 3 options: "Photo Post", "Story", "Milestone"
- Each option is a full-width `Button` with Lucide icon + label
- Sheet title: "Create"
---
## Responsive Behavior
Unchanged breakpoints from Phase 1.
| Feature | Mobile (< 768px) | Desktop (>= 1024px) |
|---------|-----------------|---------------------|
| Feed | Full-width, px-6 | 640px centered |
| Story tray | Horizontal scroll, full-width | Same (capped at 640px) |
| Story viewer | Full-screen Sheet | Centered modal overlay, max-width 480px |
| Post grid | 3-column (unchanged from desktop) | 3-column |
| Followers list | Full-width | 480px centered |
| Report sheet | Bottom slide-up | Right-side sheet, width 400px |
| Post creation | Full-width form | 512px centered form |
---
## Registry Safety
All components sourced from shadcn official registry. No third-party registries declared for Phase 2.
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official (`ui.shadcn.com`) | tabs, carousel, sheet, alert-dialog, scroll-area, popover, radio-group (additions); plus all Phase 1 components | Not required — official only |
No third-party shadcn registries are used in Phase 2. The `components.json` `"registries": {}` field is empty and must remain empty unless a vetting gate is completed.
---
## Owner Identity Subordination Rule (inherited from Phase 1 — reaffirmed for Phase 2)
1. Pet name is always the primary identity displayed at Section heading (16px, weight 600) in feed cards.
2. Owner attribution never appears on feed posts — posts are by the pet, not the owner.
3. "Managed by @owner" appears only in pet profile settings, never on content-facing surfaces.
4. All copy uses the pet as subject: "{pet name} posted", "{pet name} shared a milestone" — never the owner's name.
5. Follow/block/report actions target the pet identity, not the owner account.
---
## 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