# MOPC Platform - UI/UX Architecture ## Overview The MOPC platform uses a mobile-first responsive design built with: - **Next.js App Router** - Server Components by default - **shadcn/ui** - Accessible, customizable component library - **Tailwind CSS** - Utility-first styling - **Radix UI** - Headless accessible primitives - **Motion** (Framer Motion) - Buttery smooth animations - **Vaul** - Native-feeling mobile drawers - **Sonner** - Beautiful toast notifications - **cmdk** - Command palette (⌘K) ## Design Philosophy **CRITICAL: Avoid "AI-built" aesthetic. Platform must look professionally designed.** ### What to AVOID (typical AI-generated look) | Don't | Why | Instead | |-------|-----|---------| | Cards everywhere | Generic, lazy layout | Use varied layouts: tables, lists, grids, hero sections | | Same border-radius on everything | Monotonous | Vary: sharp corners for data, rounded for actions | | Identical padding/spacing | Robotic feel | Use intentional rhythm: tight for data, generous for CTAs | | Blue/purple gradients | Screams "AI template" | Use brand colors with restraint | | Stock icons everywhere | Impersonal | Custom icons or carefully curated set | | Centered everything | No visual hierarchy | Left-align content, strategic centering | | Gray backgrounds | Dull, corporate | Subtle off-white textures, strategic white space | | "Dashboard" with 6 equal cards | The #1 AI cliché | Prioritize: hero metric, then supporting data | ### What TO DO (professional design) | Do | Why | Example | |----|-----|---------| | Visual hierarchy | Guides the eye | Large numbers for KPIs, smaller for details | | Intentional white space | Breathability | 32-48px between sections, not uniform 16px | | Typography scale | Professional rhythm | 12/14/16/20/24/32/48px - skip sizes intentionally | | Micro-interactions | Delight users | Button hover states, loading skeletons | | Consistent but varied | Not monotonous | Same colors, different layouts per page | | Data density where needed | Efficient | Tables for lists, not cards | | Strategic color accents | Draw attention | Red only for primary CTAs, not decoration | | Real content sizes | Accommodate reality | Long project names, international characters | ## Brand Colors ```css :root { /* Brand Colors */ --color-primary: #de0f1e; /* Primary Red - CTAs, alerts */ --color-primary-hover: #c00d1a; --color-secondary: #053d57; /* Dark Blue - headers, sidebar */ --color-accent: #557f8c; /* Teal - links, secondary elements */ --color-background: #fefefe; /* White - backgrounds */ /* Semantic */ --color-success: #10b981; --color-warning: #f59e0b; --color-error: #ef4444; /* Neutrals (warm, not cold gray) */ --color-gray-50: #fafaf9; --color-gray-100: #f5f5f4; --color-gray-200: #e7e5e4; --color-gray-500: #78716c; --color-gray-900: #1c1917; } ``` ## Typography - **Font Family**: Montserrat - **Headings**: 600/700 weight - **Body**: 300/400 weight (Montserrat Light) ```css :root { --font-family: 'Montserrat', system-ui, sans-serif; --font-size-xs: 0.75rem; /* 12px */ --font-size-sm: 0.875rem; /* 14px */ --font-size-base: 1rem; /* 16px */ --font-size-lg: 1.25rem; /* 20px */ --font-size-xl: 1.5rem; /* 24px */ --font-size-2xl: 2rem; /* 32px */ --font-size-3xl: 3rem; /* 48px */ } ``` ## Design Principles 1. **Mobile First**: Base styles for mobile, enhanced for larger screens 2. **Accessibility**: WCAG 2.1 AA compliance, keyboard navigation, screen reader support 3. **Performance**: Server Components, minimal client JavaScript 4. **Consistency**: Design tokens, component library, consistent patterns 5. **Feedback**: Loading states, error messages, success confirmations ## Responsive Breakpoints ```css /* Tailwind CSS default breakpoints */ sm: 640px /* Small tablets */ md: 768px /* Tablets */ lg: 1024px /* Laptops */ xl: 1280px /* Desktops */ 2xl: 1536px /* Large monitors */ ``` ## Layout Architecture ### Application Shell ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ DESKTOP LAYOUT │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌──────────────────────────────────────────────────────┐ │ │ │ │ │ HEADER │ │ │ │ │ │ Logo Search (optional) User Menu │ │ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ SIDEBAR │ │ │ │ │ │ │ │ │ │ │ │ - Dashboard│ │ MAIN CONTENT │ │ │ │ - Rounds │ │ │ │ │ │ - Projects │ │ │ │ │ │ - Jury │ │ │ │ │ │ - Reports │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────────┘ └──────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ MOBILE LAYOUT │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ ☰ Logo User Avatar │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ MAIN CONTENT │ │ │ │ (full width) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 🏠 📋 📊 👤 │ │ │ │ Home Projects Reports Profile │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### Layout Components ```typescript // src/components/layouts/app-layout.tsx import { Sidebar } from './sidebar' import { Header } from './header' import { MobileNav } from './mobile-nav' export function AppLayout({ children }: { children: React.ReactNode }) { return (
{/* Desktop Sidebar */} {/* Main Content Area */}
{children}
{/* Mobile Bottom Navigation */}
) } ``` ## Component Hierarchy ``` src/components/ ├── ui/ # shadcn/ui base components │ ├── button.tsx │ ├── input.tsx │ ├── card.tsx │ ├── table.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── select.tsx │ ├── textarea.tsx │ ├── badge.tsx │ ├── progress.tsx │ ├── skeleton.tsx │ ├── toast.tsx │ └── ... ├── layouts/ # Layout components │ ├── app-layout.tsx │ ├── auth-layout.tsx │ ├── sidebar.tsx │ ├── header.tsx │ └── mobile-nav.tsx ├── forms/ # Form components │ ├── evaluation-form.tsx │ ├── project-import-form.tsx │ ├── round-settings-form.tsx │ └── user-invite-form.tsx ├── data-display/ # Data display components │ ├── project-card.tsx │ ├── project-list.tsx │ ├── project-table.tsx │ ├── evaluation-summary.tsx │ ├── progress-tracker.tsx │ └── stats-card.tsx └── shared/ # Shared utility components ├── file-viewer.tsx ├── loading-state.tsx ├── error-state.tsx ├── empty-state.tsx └── confirm-dialog.tsx ``` ## Page Layouts by View ### Admin Dashboard ``` ┌─────────────────────────────────────────────────────────────────┐ │ ADMIN DASHBOARD │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │ Projects │ │ Evaluations │ │ Jury Active │ │ Time Left │ │ │ │ 130 │ │ 234/390 │ │ 12/15 │ │ 5 days │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ PROGRESS BY PROJECT │ │ │ │ ████████████████████████████░░░░░░░░░░░░ 60% Complete │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ ┌────────────────────────────┐ │ │ │ JURY PROGRESS │ │ RECENT ACTIVITY │ │ │ │ │ │ │ │ │ │ Alice ████████░░ 80% │ │ • John submitted eval... │ │ │ │ Bob ██████░░░░ 60% │ │ • Sarah started eval... │ │ │ │ Carol ████░░░░░░ 40% │ │ • Admin extended window │ │ │ │ ... │ │ • ... │ │ │ └─────────────────────────────┘ └────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Mobile: Stats stack vertically, Progress & Activity in tabs ``` ### Jury Project List ``` DESKTOP: ┌─────────────────────────────────────────────────────────────────┐ │ MY ASSIGNED PROJECTS Filter ▼ Search │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Title │ Team │ Status │ Actions │ │ │ ├────────────────────────────────────────────────────────────┤ │ │ │ Ocean Cleanup AI │ BlueWave │ ✅ Done │ View │ │ │ │ Coral Restoration │ ReefGuard │ 📝 Draft │ Continue │ │ │ │ Plastic Tracker │ CleanSeas │ ⏳ Pending│ Start │ │ │ │ ... │ │ │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ Showing 1-10 of 15 < 1 2 > │ └─────────────────────────────────────────────────────────────────┘ MOBILE (Card View): ┌─────────────────────────────────────┐ │ MY PROJECTS (15) 🔍 Filter │ ├─────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────┐│ │ │ Ocean Cleanup AI ││ │ │ Team: BlueWave ││ │ │ ✅ Completed ││ │ │ [View →] ││ │ └─────────────────────────────────┘│ │ │ │ ┌─────────────────────────────────┐│ │ │ Coral Restoration ││ │ │ Team: ReefGuard ││ │ │ 📝 Draft saved ││ │ │ [Continue →] ││ │ └─────────────────────────────────┘│ │ │ │ ┌─────────────────────────────────┐│ │ │ Plastic Tracker ││ │ │ Team: CleanSeas ││ │ │ ⏳ Not started ││ │ │ [Start →] ││ │ └─────────────────────────────────┘│ │ │ └─────────────────────────────────────┘ ``` ### Evaluation Form ``` DESKTOP (Side Panel): ┌─────────────────────────────────────────────────────────────────┐ │ PROJECT DETAILS │ EVALUATION FORM │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ │ Ocean Cleanup AI │ Need Clarity │ │ Team: BlueWave Tech │ ○ 1 ○ 2 ○ 3 ● 4 ○ 5 │ │ │ │ │ [📄 Exec Summary] [📊 Deck] │ Solution Relevance │ │ [🎬 Video] │ ○ 1 ○ 2 ● 3 ○ 4 ○ 5 │ │ │ │ │ Description: │ Gap Analysis │ │ Our AI-powered system uses │ ○ 1 ○ 2 ○ 3 ○ 4 ● 5 │ │ machine learning to identify │ │ │ ocean plastic concentrations... │ Target Customers │ │ │ ○ 1 ○ 2 ○ 3 ● 4 ○ 5 │ │ Tags: AI, Plastic, Monitoring │ │ │ │ Ocean Impact │ │ │ ○ 1 ○ 2 ○ 3 ○ 4 ● 5 │ │ │ │ │ │ Global Score (1-10) │ │ │ [ 8 ] │ │ │ │ │ │ Semi-finalist? │ │ │ (●) Yes ( ) No │ │ │ │ │ │ Feedback │ │ │ ┌────────────────────┐ │ │ │ │ Strong technical │ │ │ │ │ approach with... │ │ │ │ └────────────────────┘ │ │ │ │ │ │ Autosaved 2s ago │ │ │ [Submit Evaluation] │ │ │ │ └─────────────────────────────────────────────────────────────────┘ MOBILE (Full Screen Wizard): ┌─────────────────────────────────────┐ │ ← Ocean Cleanup AI Step 3/7 │ ├─────────────────────────────────────┤ │ │ │ Gap Analysis │ │ │ │ How well does the project │ │ analyze market gaps? │ │ │ │ ┌─────────────────────────────────┐│ │ │ ││ │ │ 1 2 3 4 5 ││ │ │ (○) (○) (○) (○) (●) ││ │ │ Poor Excellent ││ │ │ ││ │ └─────────────────────────────────┘│ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────┐│ │ │ ○ ○ ● ○ ○ ○ ○ ││ │ └─────────────────────────────────┘│ │ │ │ [← Previous] [Next →] │ │ │ └─────────────────────────────────────┘ ``` ## Design System ### Color Palette (MOPC Brand) ```css /* CSS Variables in tailwind.config.ts - MOPC Brand Colors */ :root { /* Brand Colors */ --color-primary: 354 90% 47%; /* #de0f1e - Primary Red */ --color-secondary: 198 85% 18%; /* #053d57 - Dark Blue */ --color-accent: 194 25% 44%; /* #557f8c - Teal */ /* shadcn/ui mapped to MOPC brand */ --background: 0 0% 100%; /* #fefefe */ --foreground: 198 85% 18%; /* Dark Blue for text */ --card: 0 0% 100%; --card-foreground: 198 85% 18%; --popover: 0 0% 100%; --popover-foreground: 198 85% 18%; --primary: 354 90% 47%; /* Primary Red - main actions */ --primary-foreground: 0 0% 100%; --secondary: 30 6% 96%; /* Warm gray */ --secondary-foreground: 198 85% 18%; --muted: 30 6% 96%; --muted-foreground: 30 8% 45%; --accent: 194 25% 44%; /* Teal */ --accent-foreground: 0 0% 100%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 100%; --border: 30 6% 91%; --input: 30 6% 91%; --ring: 354 90% 47%; /* Primary Red for focus */ --radius: 0.5rem; /* Semantic colors */ --success: 142.1 76.2% 36.3%; --warning: 38 92% 50%; --info: 194 25% 44%; /* Teal */ } ``` ### Typography (Montserrat) ```typescript // tailwind.config.ts const config = { theme: { extend: { fontFamily: { sans: ['Montserrat', 'system-ui', 'sans-serif'], mono: ['JetBrains Mono', 'monospace'], }, fontWeight: { light: '300', normal: '400', semibold: '600', bold: '700', }, fontSize: { 'display-lg': ['3rem', { lineHeight: '1.1', fontWeight: '700' }], 'display': ['2.25rem', { lineHeight: '1.2', fontWeight: '700' }], 'heading': ['1.5rem', { lineHeight: '1.3', fontWeight: '600' }], 'subheading': ['1.125rem', { lineHeight: '1.4', fontWeight: '600' }], 'body': ['1rem', { lineHeight: '1.5', fontWeight: '400' }], 'small': ['0.875rem', { lineHeight: '1.5', fontWeight: '400' }], 'tiny': ['0.75rem', { lineHeight: '1.5', fontWeight: '400' }], }, }, }, } ``` ### Spacing System ``` Base unit: 4px 0 = 0px 1 = 4px 2 = 8px 3 = 12px 4 = 16px 5 = 20px 6 = 24px 8 = 32px 10 = 40px 12 = 48px 16 = 64px 20 = 80px 24 = 96px ``` ## Component Patterns ### Loading States ```typescript // Always show skeleton while loading function ProjectList() { const { data, isLoading } = trpc.project.list.useQuery({ roundId }) if (isLoading) { return (
{Array.from({ length: 5 }).map((_, i) => ( ))}
) } return } ``` ### Error States ```typescript // Consistent error display function ErrorState({ title = 'Something went wrong', message, onRetry, }: { title?: string message: string onRetry?: () => void }) { return (

{title}

{message}

{onRetry && ( )}
) } ``` ### Empty States ```typescript function EmptyState({ icon: Icon, title, description, action, }: { icon: React.ComponentType<{ className?: string }> title: string description: string action?: React.ReactNode }) { return (

{title}

{description}

{action &&
{action}
}
) } ``` ### Responsive Patterns ```typescript // Table on desktop, cards on mobile function ProjectDisplay({ projects }: { projects: Project[] }) { return ( <> {/* Desktop: Table */}
{/* Mobile: Cards */}
{projects.map((project) => ( ))}
) } ``` ## Touch Targets All interactive elements must have a minimum touch target of 44x44px on mobile: ```typescript // Good: Large touch target // Good: Icon button with padding ``` ## Form Patterns ### Autosave with Debounce ```typescript function EvaluationForm({ evaluation }: { evaluation: Evaluation }) { const utils = trpc.useUtils() const autosave = trpc.evaluation.autosave.useMutation({ onSuccess: () => { utils.evaluation.get.invalidate({ assignmentId: evaluation.assignmentId }) }, }) const debouncedSave = useMemo( () => debounce((data: FormData) => autosave.mutate(data), 1000), [autosave] ) return (
{ debouncedSave(data) }} > {/* Form fields */}
{autosave.isPending ? 'Saving...' : 'Autosaved'}
) } ``` ### Form Validation ```typescript const evaluationSchema = z.object({ criterionScores: z.record(z.number().min(1).max(5)), globalScore: z.number().min(1).max(10), binaryDecision: z.boolean(), feedbackText: z.string().min(10, 'Please provide at least 10 characters'), }) function EvaluationForm() { const form = useForm>({ resolver: zodResolver(evaluationSchema), }) return (
( Feedback