Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions architecture_review.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ There is no complex Role-Based Access Control (RBAC) yet. Authorization is handl

Based on our architectural review and brainstorming session, the following frontend-heavy gamification features are approved for the next development cycle before the Vercel deployment:

### 0. UI/UX Pro Max Overhaul (Modern Athletic) [DONE ✅ ]

A full transition from a generic SaaS look to a high-contrast, gamified athletic aesthetic.
- **Design Tokens:** Strict semantic CSS variables (`bg-background`, `bg-card`) ensuring perfect light/dark mode parity and OLED blacks.
- **Typography:** `next/font/google` optimized loading of **Barlow Condensed** (HUD headers/numbers) and **Barlow** (body).
- **Physical Interactions:** Replaced ad-hoc hover states with a globally consistent `.active-press` utility providing tactile, spring-physics scaling (200ms) on all interactable elements.

### 1. Interactive Human Anatomy UI [INITIALLY DONE ✅ ]

A visual representation of the human muscular system mapping to the `TargetMuscle` enum (`Chest`, `Back`, `Legs`, etc.). It will be implemented across 3 contexts:
Expand Down
41 changes: 41 additions & 0 deletions docs/superpowers/specs/2026-07-31-ui-overhaul-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Problem Statement

The current front-end UI for Fit Level Up relies heavily on generic "vibe-coded" Tailwind utilities (like slow, floaty `hover-lift` animations and generic glass panels). It lacks a unique, cohesive identity that balances the "gaming/RPG" feel with a premium fitness tracking experience. The typography and component physics feel disjointed from the core product vision.

## Solution

We will implement a "Modern Athletic" design overhaul. This replaces generic SaaS styles with a high-contrast, tactile, and gamified aesthetic. We will switch to OLED blacks, tight layout density, structured typography (Barlow Condensed + Barlow), and native-feeling spring-physics for interactions (active press states instead of floaty hovers).

## User Stories

1. As a fitness user, I want the app to feel snappy and responsive to my touch, so that navigating my workout stats feels like using a premium piece of hardware or high-end game.
2. As a user tracking my fitness streaks, I want the data to look intense and athletic, so that I feel motivated to push my limits.
3. As a developer, I want a cohesive set of Tailwind variables and utilities, so that I stop writing "vibe-code" and can reliably build new pages that look on-brand.

## Implementation Decisions

- **Art Direction:** "Modern Athletic" (Apple Fitness + Gaming).
- **Typography:** Barlow Condensed (for display/stats) and Barlow (for body text).
- **Colors:** OLED Black background (`#000000`). Flat dark surfaces for cards (`#111111`) with sharp 1px borders (`border-white/10`). Lime Green (`#d1ff1a`) remains the primary accent.
- **Motion:** Replace `hover-lift` (translate Y and shadow over 300ms) with an `active-press` utility (`scale-95` and border highlight over 100ms) to mimic physical buttons.
- **Density:** Tighten spacing arrays globally to give the app a more data-rich, HUD-like feel.
- **Modules modified:**
- `src/app/globals.css` (Base tokens and utilities)
- `src/components/ui/*` (Core primitives like Button and Card)
- `src/app/(landing)/page.tsx` (Homepage facelift)

## Testing Decisions

- A visual QA pass is required across all modified components.
- The UI must remain fully accessible (contrast ratios of text against the new flat dark backgrounds).
- Standard unit tests (`vitest`) should not be affected since this is a pure CSS/component styling refactor.

## Out of Scope

- Refactoring the entire routing or data-fetching logic.
- Adding new pages or features (only restyling existing ones).
- Changing the primary brand logo.

## Further Notes

This spec establishes the new visual baseline. Future features will inherit these tight, high-contrast primitives.
4 changes: 4 additions & 0 deletions project_briefing.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ POST /api/workouts
- Custom exercises (create, reuse across workouts)
- Full CRUD for workouts and runs (create, list with pagination, edit, delete)
- Profile page with personal records
- **UI & UX Overhaul (Modern Athletic Style):**
- Transitioned from generic SaaS design to a high-contrast, gamified athletic HUD aesthetic.
- Implemented strict CSS token hierarchy (OLED blacks, vibrant accents) and globally optimized fonts (Barlow Condensed / Barlow).
- Adopted tactile `.active-press` spring-physics animations for all interactive surfaces.
- **Interactive Human Anatomy UI:**
- 7-Day Heatmap (Dashboard widget) to visualize trained vs neglected muscles
- Live Session "Pump" Tracker (Workout Form) with dynamic SVG highlighting
Expand Down
7 changes: 2 additions & 5 deletions src/app/(app)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { PageHeader } from "@/components/layout/PageHeader";
import { XPCard } from "@/components/dashboard/XPCard";
import { ActivityTracker } from "@/components/dashboard/ActivityTracker";
import { DailyQuests } from "@/components/dashboard/DailyQuests";

Check warning on line 6 in src/app/(app)/dashboard/page.tsx

View workflow job for this annotation

GitHub Actions / test

'DailyQuests' is defined but never used
import { RecentActivity } from "@/components/dashboard/RecentActivity";

Check warning on line 7 in src/app/(app)/dashboard/page.tsx

View workflow job for this annotation

GitHub Actions / test

'RecentActivity' is defined but never used
import { DashboardTabs } from "@/components/dashboard/DashboardTabs";
import { StatCard } from "@/components/ui/StatCard";
import { getAuthUserId } from "@/lib/auth/auth-helpers";
Expand Down Expand Up @@ -32,11 +32,8 @@
const dates = allWorkouts.map(w => w.date);
const activeDays = calcActiveDays(dates);

// We already have the formatted quests from your service!
const quests = allQuests.slice(0, 5);

// 3. Filter and prepare data for the specific components
const dailyQuests = quests.filter(q => q.category === "daily");
// Filter for daily quests first, then limit to top 5
const dailyQuests = allQuests.filter(q => q.category === "daily").slice(0, 5);

const workoutDiff = stats.weeklyWorkouts - stats.lastWeekWorkouts;
let workoutTrendPercent = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export default async function ProfilePage() {
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
<div className="xl:col-span-1 space-y-6">
<ProfileHeader user={user} stats={stats} />
<PersonalRecords />
</div>

<div className="xl:col-span-2">
<AchievementGrid achievements={achievements} />
<PersonalRecords />
</div>
</div>
</div>
Expand Down
2 changes: 0 additions & 2 deletions src/app/(app)/workouts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ export default function WorkoutsPage() {
/>

<div className="xl:col-span-2 space-y-4">
<h2 className="text-lg font-semibold text-foreground">Workout History</h2>

{isLoading && <p className="text-sm text-muted">Loading...</p>}
{isError && <p className="text-sm text-accent-red">Could not load workouts</p>}

Expand Down
62 changes: 31 additions & 31 deletions src/app/(landing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,65 @@
import Link from "next/link";
import { Button } from "@/components/ui/Button";
import { Zap, Flame, Trophy, LineChart } from "lucide-react";
import { Flame, Trophy, LineChart } from "lucide-react";

export default function LandingPage() {
return (
<main className="flex-1 flex flex-col items-center justify-center p-6 md:p-12 text-center bg-black min-h-[100vh] relative overflow-hidden">
{/* Subtle glow instead of loud radial gradient for OLED contrast */}
<div className="absolute top-[-10%] left-[-10%] w-[800px] h-[800px] bg-accent-green/10 rounded-full blur-[150px] pointer-events-none -z-10" />
<main className="flex-1 flex flex-col items-center justify-center p-6 md:p-12 text-center bg-black min-h-screen relative overflow-hidden">
{/* Extremely subtle glow for OLED contrast */}
<div className="absolute top-[-10%] left-[-10%] w-200 h-200 bg-accent-green/5 rounded-full blur-[150px] pointer-events-none -z-10" />

<div className="animate-in fade-in slide-in-from-bottom-8 duration-1000 ease-out flex flex-col items-center max-w-4xl mx-auto space-y-8 pt-20 pb-16">
<div className="animate-in fade-in slide-in-from-bottom-8 duration-500 ease-out flex flex-col items-center max-w-4xl mx-auto space-y-6 pt-20 pb-16">

<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-white/5 bg-white/5 text-sm font-medium text-muted tracking-wide mb-2">
<div className="w-2 h-2 rounded-full bg-accent-green" />
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-accent-green/20 bg-accent-green/10 text-xs font-bold text-accent-green uppercase tracking-widest mb-4">
YOUR FITNESS JOURNEY, REIMAGINED
</div>

<h1 className="text-6xl md:text-8xl font-bold text-foreground tracking-tight leading-[1.1]">
<h1 className="text-7xl md:text-9xl font-display font-bold text-foreground tracking-tight uppercase leading-[0.9]">
Level Up <br className="hidden md:block"/>
Your <span className="text-accent-green">Fitness</span>
</h1>

<p className="text-xl md:text-2xl text-muted max-w-3xl leading-relaxed font-normal mt-4 mb-8">
Gamify your workouts, earn XP, complete daily quests, and unlock epic achievements. Your fitness journey is now an RPG.
<p className="text-xl md:text-2xl text-muted max-w-2xl leading-relaxed font-medium mt-6 mb-8">
Gamify your workouts. Earn XP. Complete daily quests.
<br className="hidden md:block"/> Your fitness journey is now an RPG.
</p>

<div className="flex flex-col sm:flex-row items-center gap-4 pt-4">
<div className="flex flex-col sm:flex-row items-center gap-4 pt-6">
<Link href="/signup">
<Button className="px-8 py-6 text-lg bg-accent-green text-black hover:bg-accent-green/90 transition-all duration-300 rounded-full font-semibold flex items-center gap-2">
Get Started Now <span className="text-xl leading-none">→</span>
<Button className="px-10 py-6 text-lg rounded-full gap-2">
GET STARTED NOW <span className="text-xl leading-none">→</span>
</Button>
</Link>
<Link href="#how-it-works">
<Button variant="outline" className="px-8 py-6 text-lg bg-transparent border-white/10 text-foreground hover:bg-white/5 transition-all duration-300 rounded-full font-semibold">
See How It Works
<Button variant="outline" className="px-10 py-6 text-lg rounded-full">
SEE HOW IT WORKS
</Button>
</Link>
</div>

<div className="grid grid-cols-1 md:grid-cols-3 gap-8 pt-24 w-full">
<div className="flex flex-col items-center text-center gap-4 p-8 rounded-3xl glass-panel hover-lift">
<div className="w-16 h-16 rounded-2xl bg-accent-orange/10 flex items-center justify-center mb-2">
<Flame className="w-8 h-8 text-accent-orange" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 pt-24 w-full">
<div className="flex flex-col items-center text-center gap-4 p-8 rounded-2xl glass-panel active-press hover:border-accent-orange/50 transition-default">
<div className="w-14 h-14 rounded-xl bg-accent-orange/10 flex items-center justify-center mb-2">
<Flame className="w-7 h-7 text-accent-orange" />
</div>
<h3 className="font-bold text-2xl tracking-tight">Build Streaks</h3>
<p className="text-base text-muted/80 leading-relaxed">Stay consistent and watch your streak multiplier grow effortlessly.</p>
<h3 className="font-display font-bold uppercase text-3xl tracking-tight">Build Streaks</h3>
<p className="text-sm text-muted leading-relaxed">Stay consistent and watch your streak multiplier grow effortlessly.</p>
</div>

<div className="flex flex-col items-center text-center gap-4 p-8 rounded-3xl glass-panel hover-lift">
<div className="w-16 h-16 rounded-2xl bg-accent-purple/10 flex items-center justify-center mb-2">
<Trophy className="w-8 h-8 text-accent-purple" />
<div className="flex flex-col items-center text-center gap-4 p-8 rounded-2xl glass-panel active-press hover:border-accent-purple/50 transition-default">
<div className="w-14 h-14 rounded-xl bg-accent-purple/10 flex items-center justify-center mb-2">
<Trophy className="w-7 h-7 text-accent-purple" />
</div>
<h3 className="font-bold text-2xl tracking-tight">Unlock Badges</h3>
<p className="text-base text-muted/80 leading-relaxed">Earn rare achievements for pushing your limits and reaching milestones.</p>
<h3 className="font-display font-bold uppercase text-3xl tracking-tight">Unlock Badges</h3>
<p className="text-sm text-muted leading-relaxed">Earn rare achievements for pushing your limits and reaching milestones.</p>
</div>

<div className="flex flex-col items-center text-center gap-4 p-8 rounded-3xl glass-panel hover-lift">
<div className="w-16 h-16 rounded-2xl bg-accent-blue/10 flex items-center justify-center mb-2">
<LineChart className="w-8 h-8 text-accent-blue" />
<div className="flex flex-col items-center text-center gap-4 p-8 rounded-2xl glass-panel active-press hover:border-accent-blue/50 transition-default">
<div className="w-14 h-14 rounded-xl bg-accent-blue/10 flex items-center justify-center mb-2">
<LineChart className="w-7 h-7 text-accent-blue" />
</div>
<h3 className="font-bold text-2xl tracking-tight">Track Progress</h3>
<p className="text-base text-muted/80 leading-relaxed">Watch your stats improve over time with highly detailed analytics.</p>
<h3 className="font-display font-bold uppercase text-3xl tracking-tight">Track Progress</h3>
<p className="text-sm text-muted leading-relaxed">Watch your stats improve over time with highly detailed analytics.</p>
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// implement api calls for health checks
75 changes: 42 additions & 33 deletions src/app/api/runs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,57 @@
import { logRun } from "@/lib/services/runs/log-run";
import { getAuthUserId } from "@/lib/auth/auth-helpers";
import { CreateRunSchema } from "@/lib/validations/schemas";
import { z } from "zod";

Check warning on line 6 in src/app/api/runs/route.ts

View workflow job for this annotation

GitHub Actions / test

'z' is defined but never used
import { RateLimit } from "@/lib/auth/rate-limit";
import { handleApiError } from "@/lib/api/handle-api-error";

export async function GET(request: Request) {
try {
const userId = await getAuthUserId();
const { searchParams } = new URL(request.url);
let limit = searchParams.has("limit") ? parseInt(searchParams.get("limit")!) : 5;
if (isNaN(limit) || limit < 1) limit = 5;
if (limit > 50) limit = 50;
const page = searchParams.has("page") ? parseInt(searchParams.get("page")!) : 1;
const skip = (page - 1) * limit;
try {
const userId = await getAuthUserId();
const { searchParams } = new URL(request.url);
let limit = searchParams.has("limit")
? parseInt(searchParams.get("limit")!)
: 5;
if (isNaN(limit) || limit < 1) limit = 5;
//TODO: turn into constant . avoid magic numbers
if (limit > 50) limit = 50;
const page = searchParams.has("page")
? parseInt(searchParams.get("page")!)
: 1;
const skip = (page - 1) * limit;

const { data, totalCount } = await getPaginatedRunsFromDb(userId, limit, skip);
const totalPages = Math.ceil(totalCount / limit);

return NextResponse.json({
data,
totalCount,
totalPages,
currentPage: page
});
} catch (err) {
return handleApiError(err);
}
const { data, totalCount } = await getPaginatedRunsFromDb(
userId,
limit,
skip,
);
const totalPages = Math.ceil(totalCount / limit);

return NextResponse.json({
data,
totalCount,
totalPages,
currentPage: page,
});
} catch (err) {
return handleApiError(err);
}
}

export async function POST(request: Request) {
try {
const userId = await getAuthUserId();
try {
const userId = await getAuthUserId();

const { success } = await RateLimit.limit(userId);
if (!success) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}

const body = await request.json();
const parsed = CreateRunSchema.parse(body);
const run = await logRun(parsed, userId);
return NextResponse.json(run, { status: 201 });
} catch (err) {
return handleApiError(err);
const { success } = await RateLimit.limit(userId);
if (!success) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}

const body = await request.json();
const parsed = CreateRunSchema.parse(body);
const run = await logRun(parsed, userId);
return NextResponse.json(run, { status: 201 });
} catch (err) {
return handleApiError(err);
}
}
49 changes: 18 additions & 31 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
@import "tailwindcss";

/* These CSS variables define our entire color system.
Updated to OLED-friendly Apple-vibe. */
:root {
--background: #000000;
--foreground: #fcfcfc;
/* Slightly lighter, elevated slate/zinc for cards to contrast against black */
--card: #18181b;
--card-hover: #27272a;
--border: #3f3f46;
--card: #111111;
--card-hover: #18181b;
--border: #27272a;
--muted: #a1a1aa;

/* Retaining app accent colors but tweaking to Apple-like vibrance */
--accent-green: #d1ff1a; /* Bright Lime Green */
--accent-orange: #ff9500; /* iOS Orange */
--accent-purple: #af52de; /* iOS Purple */
--accent-blue: #007aff; /* iOS Blue */
--accent-red: #ff3b30; /* iOS Red */
--accent-green: #d1ff1a;
--accent-orange: #ff9500;
--accent-purple: #af52de;
--accent-blue: #007aff;
--accent-red: #ff3b30;
}

/* @theme inline tells Tailwind to generate utility classes */
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
Expand All @@ -32,9 +27,10 @@
--color-accent-purple: var(--accent-purple);
--color-accent-blue: var(--accent-blue);
--color-accent-red: var(--accent-red);
--font-sans: var(--font-inter);

/* Systematically scale up typography for better readability globally */
--font-sans: var(--font-barlow), system-ui, sans-serif;
--font-display: var(--font-barlow-condensed), system-ui, sans-serif;

--text-xs: 0.875rem;
--text-xs--line-height: 1.25rem;
--text-sm: 1rem;
Expand All @@ -50,46 +46,37 @@
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans), system-ui, sans-serif;
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

@utility glass-panel {
background: linear-gradient(145deg, rgba(39, 39, 42, 0.7), rgba(24, 24, 27, 0.8));
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
background: var(--card);
border: 1px solid rgba(255, 255, 255, 0.05);
}

@utility hover-lift {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
@utility active-press {
transition: transform 0.1s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.2s ease-out;
&:active {
transform: scale(0.96);
}
}

/* Smooth transitions for interactive elements */
.transition-default {
transition: all 0.2s ease-in-out;
}

/* Custom scrollbar for dark theme */
::-webkit-scrollbar {
width: 6px;
}

::-webkit-scrollbar-track {
background: var(--background);
}

::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
background: var(--muted);
}
Loading
Loading