diff --git a/docs/superpowers/plans/2026-05-16-frutiger-aero-redesign.md b/docs/superpowers/plans/2026-05-16-frutiger-aero-redesign.md new file mode 100644 index 0000000..cb56b36 --- /dev/null +++ b/docs/superpowers/plans/2026-05-16-frutiger-aero-redesign.md @@ -0,0 +1,1198 @@ +# Thoughts Frontend — Frutiger Aero Redesign Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Apply the Frutiger Aero aesthetic throughout `thoughts-frontend` — glass panels, gloss sweeps, Aero shimmer, gradient avatars, and delightful interaction moments (particle bursts, shake+fade, slide-in forms). + +**Architecture:** Component-by-component update. CSS keyframes and utility classes go in `globals.css` first (foundation). UI primitives (badge, skeleton) are updated next. Then page-level components (header, landing). Then feed components (cards, widgets). Then interaction moments (follow burst, delete animation). Every task ends with a build check and a commit. + +**Tech Stack:** Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS v4, shadcn/ui, Bun + +**Build check command (run after every task):** +```bash +cd thoughts-frontend && bun run build +``` + +**Spec:** `docs/superpowers/specs/2026-05-16-thoughts-frutiger-aero-redesign-design.md` + +--- + +## Pre-flight: verify current build passes + +- [ ] Run `cd thoughts-frontend && bun run build` — must be green before starting + +--- + +## Task 1: CSS keyframes and utility classes + +**Files:** +- Modify: `thoughts-frontend/app/globals.css` + +Add after the last `@layer components { }` block. + +- [ ] **Step 1: Append keyframes and utilities to `globals.css`** + +Add this block at the end of the file: + +```css +/* ── Frutiger Aero interaction keyframes ── */ +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-8px); + max-height: 0; + overflow: hidden; + } + to { + opacity: 1; + transform: translateY(0); + max-height: 300px; + overflow: hidden; + } +} + +@keyframes shake { + 0%, 100% { transform: translateX(0) rotate(0deg); } + 15% { transform: translateX(-4px) rotate(-1.5deg); } + 30% { transform: translateX(4px) rotate(1.5deg); } + 45% { transform: translateX(-3px) rotate(-1deg); } + 60% { transform: translateX(3px) rotate(1deg); } + 75% { transform: translateX(-1px) rotate(-0.5deg); } +} + +@keyframes fadeOut { + from { opacity: 1; transform: scale(1) translateY(0); } + to { opacity: 0; transform: scale(0.9) translateY(8px); } +} + +@keyframes floatBob { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-6px); } +} + +@keyframes shimmerAero { + 0% { background-position: -400px 0; } + 100% { background-position: 400px 0; } +} + +@layer components { + .animate-slide-down { + animation: slideDown 0.22s ease-out forwards; + } + .animate-shake { + animation: shake 0.45s ease-out; + } + .animate-fade-out { + animation: fadeOut 0.3s ease-out forwards; + } + .animate-float-bob { + animation: floatBob 2.8s ease-in-out infinite; + } + + /* Aero-tinted shimmer for skeleton loaders */ + .shimmer-aero { + background: linear-gradient( + 90deg, + rgba(96, 165, 250, 0.12) 25%, + rgba(96, 165, 250, 0.30) 50%, + rgba(96, 165, 250, 0.12) 75% + ); + background-size: 800px 100%; + animation: shimmerAero 1.5s infinite linear; + } + + /* Widget title icon badges */ + .widget-icon { + width: 22px; + height: 22px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + flex-shrink: 0; + } + .widget-icon-blue { + background: linear-gradient(135deg, #60a5fa, #2563eb); + box-shadow: 0 2px 4px rgba(37, 99, 235, 0.3), + inset 0 1px 1px rgba(255, 255, 255, 0.3); + } + .widget-icon-green { + background: linear-gradient(135deg, #6ee7b7, #10b981); + box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3), + inset 0 1px 1px rgba(255, 255, 255, 0.3); + } + .widget-icon-purple { + background: linear-gradient(135deg, #c4b5fd, #7c3aed); + box-shadow: 0 2px 4px rgba(124, 58, 237, 0.3), + inset 0 1px 1px rgba(255, 255, 255, 0.3); + } + + /* Landing page ambient orbs */ + .orb { + position: absolute; + border-radius: 50%; + filter: blur(40px); + opacity: 0.45; + pointer-events: none; + } + + /* Gradient avatar fallback */ + .avatar-gradient { + background: linear-gradient(135deg, #60a5fa, #34d399); + box-shadow: 0 0 0 2px white, 0 0 0 3.5px rgba(59, 130, 246, 0.45); + } +} + +/* Respect reduced motion */ +@media (prefers-reduced-motion: reduce) { + .animate-slide-down { animation: none; } + .animate-shake { animation: none; } + .animate-fade-out { animation: none; } + .animate-float-bob { animation: none; } + .shimmer-aero { + animation: none; + background: rgba(96, 165, 250, 0.18); + } +} +``` + +- [ ] **Step 2: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +Expected: build succeeds with no errors. + +- [ ] **Step 3: Commit** + +```bash +git add thoughts-frontend/app/globals.css +git commit -m "feat: add FA keyframes and utility classes to globals.css" +``` + +--- + +## Task 2: Badge `branded` and `trending` variants + +**Files:** +- Modify: `thoughts-frontend/components/ui/badge.tsx` + +- [ ] **Step 1: Add variants to `badgeVariants` in `badge.tsx`** + +Replace the `variants` object inside `cva(...)`: + +```tsx +variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80 glossy-effect bottom text-shadow-sm", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80 glossy-effect bottom text-shadow-sm", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80 glossy-effect bottom text-shadow-sm", + outline: "text-foreground glossy-effect bottom text-shadow-sm", + branded: + "border border-primary/20 bg-primary/8 text-primary font-semibold hover:bg-primary/15 hover:scale-105 transition-transform cursor-pointer", + trending: + "border border-red-300/30 bg-gradient-to-r from-orange-500/10 to-red-500/8 text-red-600 font-semibold hover:from-orange-500/18 hover:to-red-500/14 hover:scale-105 transition-transform cursor-pointer", + }, +}, +``` + +- [ ] **Step 2: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +Expected: build succeeds. + +- [ ] **Step 3: Commit** + +```bash +git add thoughts-frontend/components/ui/badge.tsx +git commit -m "feat: add branded and trending badge variants" +``` + +--- + +## Task 3: Skeleton Aero shimmer + +**Files:** +- Modify: `thoughts-frontend/components/ui/skeleton.tsx` + +- [ ] **Step 1: Read current `skeleton.tsx`** + +```bash +cat thoughts-frontend/components/ui/skeleton.tsx +``` + +- [ ] **Step 2: Replace the file with Aero shimmer version** + +```tsx +import * as React from "react" +import { cn } from "@/lib/utils" + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Skeleton } +``` + +- [ ] **Step 3: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +- [ ] **Step 4: Commit** + +```bash +git add thoughts-frontend/components/ui/skeleton.tsx +git commit -m "feat: apply Aero shimmer to skeleton loader" +``` + +--- + +## Task 4: UserAvatar — gradient fallback + glow ring + +**Files:** +- Modify: `thoughts-frontend/components/user-avatar.tsx` + +Current: fallback is a generic `` icon, border is `border-primary/50`. + +- [ ] **Step 1: Update `user-avatar.tsx`** + +```tsx +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { cn } from "@/lib/utils"; + +interface UserAvatarProps { + src?: string | null; + alt?: string | null; + className?: string; +} + +export function UserAvatar({ src, alt, className }: UserAvatarProps) { + const initial = alt?.trim()[0]?.toUpperCase() ?? "?"; + + return ( + + {src && ( + + )} + + {initial} + + + ); +} +``` + +- [ ] **Step 2: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +- [ ] **Step 3: Commit** + +```bash +git add thoughts-frontend/components/user-avatar.tsx +git commit -m "feat: gradient avatar fallback with initials and glow ring" +``` + +--- + +## Task 5: EmptyState redesign + +**Files:** +- Modify: `thoughts-frontend/components/empty-state.tsx` + +Current: renders a single `

` with the message string. + +- [ ] **Step 1: Rewrite `empty-state.tsx`** + +```tsx +import Link from "next/link"; + +interface EmptyStateProps { + emoji?: string; + title?: string; + message: string; + ctaLabel?: string; + ctaHref?: string; + className?: string; +} + +export function EmptyState({ + emoji = "💭", + title, + message, + ctaLabel, + ctaHref, + className = "", +}: EmptyStateProps) { + return ( +

+ + {emoji} + + {title && ( +

{title}

+ )} +

{message}

+ {ctaLabel && ctaHref && ( + + {ctaLabel} + + )} +
+ ); +} +``` + +- [ ] **Step 2: Update all call sites to pass the new props** + +Search for existing usages: +```bash +grep -rn "EmptyState" thoughts-frontend/app --include="*.tsx" +``` + +For each usage, add an `emoji` and `title` appropriate to the context. For example in `app/page.tsx`: + +```tsx + +``` + +For search (`app/search/page.tsx`) — check the file and use `emoji="🔍" title="No results"`. + +For tags — use `emoji="🏷" title="No thoughts here yet"`. + +- [ ] **Step 3: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +Expected: no TypeScript errors — `message` is still required, other props are optional. + +- [ ] **Step 4: Commit** + +```bash +git add thoughts-frontend/components/empty-state.tsx thoughts-frontend/app +git commit -m "feat: redesign EmptyState with floating emoji and optional CTA" +``` + +--- + +## Task 6: Header — logo bubble + pill buttons + +**Files:** +- Modify: `thoughts-frontend/components/header.tsx` + +Current: plain text "Thoughts", flat Login/Register buttons. + +- [ ] **Step 1: Rewrite `header.tsx`** + +```tsx +"use client"; + +import { useAuth } from "@/hooks/use-auth"; +import Link from "next/link"; +import { Button } from "./ui/button"; +import { UserNav } from "./user-nav"; +import { MainNav } from "./main-nav"; + +export function Header() { + const { token } = useAuth(); + + return ( +
+
+ {/* Logo */} + +
+ 💭 +
+ + Thoughts + + + + + +
+ {token ? ( + + ) : ( + <> + + + + )} +
+
+
+ ); +} +``` + +- [ ] **Step 2: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +- [ ] **Step 3: Commit** + +```bash +git add thoughts-frontend/components/header.tsx +git commit -m "feat: add logo bubble and pill buttons to header" +``` + +--- + +## Task 7: Landing page — orbs, deeper glass, fediverse badge + +**Files:** +- Modify: `thoughts-frontend/app/page.tsx` (the `LandingPage` function only) + +- [ ] **Step 1: Replace the `LandingPage` function** + +Find and replace the entire `LandingPage` function (lines 122–148): + +```tsx +function LandingPage() { + return ( +
+ {/* Ambient orbs */} +
+
+
+ + {/* Hero card */} +
+ {/* Gloss sweep */} +
+ +

+ Welcome to Thoughts ✨ +

+

+ A federated social network for short-form thoughts.
+ Connect with the Fediverse. +

+ +
+ + +
+ + {/* Fediverse badge */} +
+ + + Works with Mastodon, Pixelfed & more + +
+
+
+ ); +} +``` + +- [ ] **Step 2: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +- [ ] **Step 3: Commit** + +```bash +git add thoughts-frontend/app/page.tsx +git commit -m "feat: redesign landing page with ambient orbs and fediverse badge" +``` + +--- + +## Task 8: Thought card — hover lift, reply button pill, hashtag coloring + +**Files:** +- Modify: `thoughts-frontend/components/thought-card.tsx` + +- [ ] **Step 1: Add `renderWithHashtags` helper before the component** + +Add this function just above the `ThoughtCard` function declaration: + +```tsx +function renderWithHashtags(content: string) { + return content.split(/(#\w+)/g).map((part, i) => + /^#\w+$/.test(part) ? ( + + {part} + + ) : ( + part + ) + ); +} +``` + +- [ ] **Step 2: Add hover lift to the `` element** + +Find the `` line and change it to: + +```tsx + +``` + +- [ ] **Step 3: Replace the Reply ` +
+ ) +} +``` + +- [ ] **Step 2: Build check** + +```bash +cd thoughts-frontend && bun run build +``` + +- [ ] **Step 3: Commit** + +```bash +git add thoughts-frontend/components/follow-button.tsx +git commit -m "feat: canvas particle burst on follow" +``` + +--- + +## Final: visual verification checklist + +Start the dev server and walk through each surface: + +```bash +cd thoughts-frontend && bun run dev +``` + +- [ ] **Landing page** — background image visible through orbs, hero card is glassy, buttons are pill-shaped, fediverse badge shows +- [ ] **Header** — 💭 logo bubble, glass blur on scroll, pill Login/Register +- [ ] **Feed** — cards have glass treatment, hover lifts card, hashtags are blue +- [ ] **Reply** — clicking Reply slides form in smoothly, clicking Cancel hides it +- [ ] **Delete** — confirm in alert dialog → card shakes → card fades → gone +- [ ] **Follow button** — particles burst on follow, button turns green +- [ ] **Popular Tags** — 🔥 on top 2 tags, branded blue pills for rest, icon badge +- [ ] **Top Friends** — gradient avatars, username handle, following badge +- [ ] **Community widget** — gradient number text, purple icon badge +- [ ] **Loading skeletons** — blue/teal shimmer instead of grey +- [ ] **Empty states** — floating emoji, friendly copy, glossy CTA where applicable +- [ ] **Mobile** — at 390px width: no sidebar visible, cards full-width, header collapses correctly