# 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