- Add i18n support (EN/ZH) with language toggle - Redesign header with education dropdown, contact copy buttons - Add RBC project showcase with bullet points, skills highlighting - Create terraform-drift placeholder page - Add ProjectShowcase component with media toggle (image/video) - Update experience timeline with action-driven descriptions - Add Docker + Forgejo CI workflow for image builds - Update K8s manifests for riotpiao.com ingress - Add DESIGN.md documenting design system patterns
342 lines
7.7 KiB
Markdown
342 lines
7.7 KiB
Markdown
# Portfolio Design System
|
|
|
|
## Color Palette
|
|
|
|
### Primary Colors
|
|
- **Blue**: `blue-600` (primary actions, links, accents)
|
|
- **Purple**: `purple-600` (gradients, secondary accents)
|
|
- **Green**: `green-500` (live status, success)
|
|
|
|
### Neutral Colors
|
|
- **Light Mode**:
|
|
- Background: `white`, `gray-50`
|
|
- Text Primary: `gray-900`
|
|
- Text Secondary: `gray-600`, `gray-700`
|
|
- Borders: `gray-200`, `gray-300`
|
|
|
|
- **Dark Mode**:
|
|
- Background: `gray-950`, `gray-900`
|
|
- Text Primary: `white`
|
|
- Text Secondary: `gray-400`, `gray-300`
|
|
- Borders: `gray-800`, `gray-700`
|
|
|
|
### Status Colors
|
|
- Live: `green-100/900` bg, `green-800/200` text
|
|
- Building: `blue-100/900` bg, `blue-800/200` text
|
|
- Planning: `gray-100/800` bg, `gray-800/200` text
|
|
|
|
### Timeline Colors (gradients)
|
|
- riotpiao.com: `from-green-500 to-green-600`
|
|
- RBC: `from-red-500 to-red-600`
|
|
- AWS: `from-orange-500 to-orange-600`
|
|
- Titus: `from-cyan-500 to-cyan-600`
|
|
- NAV Canada: `from-blue-500 to-blue-600`
|
|
|
|
---
|
|
|
|
## Typography
|
|
|
|
### Font Sizes
|
|
- Hero Title: `text-4xl md:text-5xl font-bold`
|
|
- Section Title: `text-4xl font-bold`
|
|
- Card Title: `text-2xl font-bold` or `text-xl font-bold`
|
|
- Subtitle: `text-xl font-medium` or `text-lg`
|
|
- Body: `text-sm` or `text-base`
|
|
- Caption: `text-xs`
|
|
- Mono/Code: `font-mono text-xs`
|
|
|
|
### Font Weights
|
|
- Bold: `font-bold` (titles, emphasis)
|
|
- Semibold: `font-semibold` (subtitles, labels)
|
|
- Medium: `font-medium` (secondary text)
|
|
|
|
---
|
|
|
|
## Spacing
|
|
|
|
### Layout
|
|
- Max Width: `max-w-6xl` (main container), `max-w-4xl` (content), `max-w-3xl` (cards), `max-w-5xl` (hero image)
|
|
- Padding: `px-6` (horizontal), `py-20` or `py-24` (section vertical)
|
|
- Gap: `gap-8` (cards), `gap-6` (nav items), `gap-4` (buttons), `gap-2` (tags)
|
|
|
|
### Component Spacing
|
|
- Section margin bottom: `mb-16`
|
|
- Card padding: `p-6`
|
|
- Button padding: `px-8 py-3` (large), `px-4 py-2` (medium), `px-3 py-1` (small)
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
### Buttons
|
|
|
|
**Primary Button**
|
|
```jsx
|
|
className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-semibold transition"
|
|
```
|
|
|
|
**Secondary/Outline Button**
|
|
```jsx
|
|
className="border-2 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-800 px-4 py-2 rounded-lg font-semibold transition"
|
|
```
|
|
|
|
**Language Toggle**
|
|
```jsx
|
|
className="px-3 py-1 text-sm font-semibold rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
```
|
|
|
|
**Icon Button**
|
|
```jsx
|
|
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded"
|
|
```
|
|
|
|
### Cards
|
|
|
|
**Standard Card**
|
|
```jsx
|
|
className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 p-6 hover:shadow-lg transition-shadow"
|
|
```
|
|
|
|
**Project Showcase Card**
|
|
```jsx
|
|
className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden hover:shadow-lg transition-shadow"
|
|
```
|
|
|
|
### Tags/Pills
|
|
|
|
**Skill Tag**
|
|
```jsx
|
|
className="text-xs px-3 py-1 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300"
|
|
```
|
|
|
|
**Status Badge**
|
|
```jsx
|
|
// Live
|
|
className="px-3 py-1 rounded-full text-xs font-semibold bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200"
|
|
// Building
|
|
className="px-3 py-1 rounded-full text-xs font-semibold bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
|
|
```
|
|
|
|
### Dropdowns
|
|
|
|
**Dropdown Container**
|
|
```jsx
|
|
className="absolute right-0 mt-2 w-64 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg p-4 space-y-3"
|
|
```
|
|
|
|
### Modals
|
|
|
|
**Modal Overlay**
|
|
```jsx
|
|
className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4"
|
|
```
|
|
|
|
**Modal Content**
|
|
```jsx
|
|
className="bg-white dark:bg-gray-900 rounded-lg shadow-2xl max-w-2xl w-full max-h-96 overflow-y-auto"
|
|
```
|
|
|
|
**Modal Header (Gradient)**
|
|
```jsx
|
|
className="sticky top-0 bg-gradient-to-r from-blue-600 to-purple-600 px-6 py-6 flex justify-between items-center"
|
|
```
|
|
|
|
---
|
|
|
|
## Animations (Framer Motion)
|
|
|
|
### Fade In Up
|
|
```jsx
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.8, delay: 0.2 }}
|
|
```
|
|
|
|
### Scale In
|
|
```jsx
|
|
initial={{ scale: 0, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ duration: 0.8, delay: 0.2 }}
|
|
```
|
|
|
|
### Stagger Children
|
|
```jsx
|
|
variants={{
|
|
hidden: { opacity: 0 },
|
|
visible: {
|
|
opacity: 1,
|
|
transition: { staggerChildren: 0.15 },
|
|
},
|
|
}}
|
|
```
|
|
|
|
### Role Text Rotation
|
|
```jsx
|
|
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
exit={{ opacity: 0, y: -20, scale: 0.95 }}
|
|
transition={{ duration: 0.6, ease: 'easeInOut' }}
|
|
```
|
|
|
|
### Background Pulse
|
|
```jsx
|
|
animate={{
|
|
scale: [1, 1.15, 1],
|
|
opacity: [0.25, 0.4, 0.25],
|
|
}}
|
|
transition={{
|
|
duration: 6,
|
|
repeat: Infinity,
|
|
ease: 'easeInOut',
|
|
}}
|
|
```
|
|
|
|
---
|
|
|
|
## Gradients
|
|
|
|
### Hero Background
|
|
```jsx
|
|
className="bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-950 dark:to-gray-900"
|
|
```
|
|
|
|
### Timeline Line
|
|
```jsx
|
|
className="bg-gradient-to-b from-blue-500 via-purple-500 to-green-500"
|
|
```
|
|
|
|
### Stat Highlight
|
|
```jsx
|
|
className="bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800"
|
|
```
|
|
|
|
### Modal Header
|
|
```jsx
|
|
className="bg-gradient-to-r from-blue-600 to-purple-600"
|
|
```
|
|
|
|
---
|
|
|
|
## Z-Index Layers
|
|
|
|
- `z-0`: Background images
|
|
- `z-10`: Content overlays (avatar, text)
|
|
- `z-20`: Avatar + rotating text group
|
|
- `z-40`: Terminal button
|
|
- `z-50`: Header, Modals
|
|
|
|
---
|
|
|
|
## Responsive Breakpoints
|
|
|
|
- Mobile: Default (no prefix)
|
|
- Tablet/Desktop: `md:` (768px+)
|
|
- Large Desktop: `lg:` (1024px+)
|
|
|
|
### Common Patterns
|
|
```jsx
|
|
// Hide on mobile, show on desktop
|
|
className="hidden md:flex"
|
|
|
|
// Show on mobile, hide on desktop
|
|
className="md:hidden"
|
|
|
|
// Responsive grid
|
|
className="grid grid-cols-1 md:grid-cols-2 gap-8"
|
|
|
|
// Responsive text
|
|
className="text-4xl md:text-5xl"
|
|
|
|
// Responsive padding
|
|
className="px-4 md:px-6"
|
|
```
|
|
|
|
---
|
|
|
|
## Icons (Lucide React)
|
|
|
|
### Sizes
|
|
- Navigation: `size={20}`
|
|
- Buttons: `size={16}`
|
|
- Cards: `size={24}`
|
|
- Hero: `size={32}`
|
|
- Close: `size={24}`
|
|
|
|
### Common Icons
|
|
- `Menu`, `X` - Mobile nav
|
|
- `Moon`, `Sun` - Theme toggle
|
|
- `ExternalLink` - External links
|
|
- `Network`, `Shield`, `Code`, `Sparkles`, `Zap`, `Cpu` - Role icons
|
|
- `GitBranch`, `Database`, `MessageSquare`, `Layers`, `BarChart3` - Feature icons
|
|
|
|
---
|
|
|
|
## Internationalization (i18n)
|
|
|
|
### Structure
|
|
```
|
|
lib/
|
|
translations.json # All translations
|
|
LanguageContext.tsx # React context provider
|
|
```
|
|
|
|
### Usage
|
|
```jsx
|
|
import { useLanguage } from '@/lib/LanguageContext'
|
|
|
|
const { lang, setLang, t } = useLanguage()
|
|
|
|
// Access translations
|
|
<h1>{t.hero.title}</h1>
|
|
```
|
|
|
|
### Language Toggle Button
|
|
Shows opposite language:
|
|
- When `en`: Shows `中文`
|
|
- When `zh`: Shows `EN`
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
```
|
|
components/
|
|
Header.tsx # Navigation, dropdowns, language toggle
|
|
HeroBlobFlow.tsx # Hero with avatar, rotating roles
|
|
ExperienceTimeline.tsx # Work history timeline
|
|
ProjectShowcase.tsx # Project cards
|
|
InteractiveTerminal.tsx # Cmd+K terminal
|
|
TypewriterText.tsx # Typewriter effect
|
|
|
|
lib/
|
|
TerminalContext.tsx # Terminal open/close state
|
|
LanguageContext.tsx # Language state + translations
|
|
translations.json # EN/ZH translations
|
|
|
|
app/
|
|
page.tsx # Main page
|
|
layout.tsx # Root layout with providers
|
|
globals.css # Tailwind + global styles
|
|
|
|
public/
|
|
avatar.jpeg # Profile photo
|
|
hire-me.png # Dark mode hero background
|
|
hire-me-light.png # Light mode hero background
|
|
```
|
|
|
|
---
|
|
|
|
## Dark Mode
|
|
|
|
Implemented via Tailwind's `dark:` prefix. Toggle class on `<html>` element.
|
|
|
|
```jsx
|
|
// Toggle
|
|
document.documentElement.classList.toggle('dark')
|
|
|
|
// Check
|
|
document.documentElement.classList.contains('dark')
|
|
```
|
|
|
|
Observe changes via MutationObserver for components that need to react to theme changes (e.g., background image swap).
|