diff --git a/.planning/phases/02-content-core/02-UI-SPEC.md b/.planning/phases/02-content-core/02-UI-SPEC.md new file mode 100644 index 0000000..a94c9bf --- /dev/null +++ b/.planning/phases/02-content-core/02-UI-SPEC.md @@ -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