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:
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
|
||||
Reference in new issue
Block a user