feat: portfolio redesign with i18n, RBC project showcase, CI/CD
- 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
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user