# Portfolio Tết Theme - Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development OR superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
**Goal:** Build a fully polished, production-ready portfolio with Next.js 14, Tailwind, Framer Motion, and Tết theme.
**Architecture:** Next.js App Router with TypeScript, components in `components/`, data in `data/`, effects in `components/TetEffects.tsx`. Single-page Home with scroll sections; separate pages for Projects and My Corner blog.
**Tech Stack:** Next.js 14+, TypeScript, Tailwind CSS, Framer Motion, Google Fonts.
---
### Task 1: Initialize Next.js project
**Files:**
- Create: `package.json`
- Create: `next.config.js`
- Create: `tsconfig.json`
- Create: `tailwind.config.ts`
- Create: `postcss.config.mjs`
- Create: `app/layout.tsx`
- Create: `app/page.tsx`
- Create: `app/globals.css`
- [ ] **Step 1:** Run create-next-app with TypeScript and Tailwind
```bash
cd /home/node/.openclaw/workspace/projects/clawteam-portfolio
npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --no-turbopack --import-alias "@/*"
```
Accept defaults. This creates the base structure.
- [ ] **Step 2:** Install additional dependencies
```bash
npm install framer-motion
npm install -D @types/node
```
- [ ] **Step 3:** Configure Tailwind colors for Tết theme
Edit `tailwind.config.ts`:
```ts
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
primary: {
50: '#FFEBEE',
100: '#FFCDD2',
200: '#EF9A9A',
300: '#E57373',
400: '#EF5350',
500: '#F44336',
600: '#D32F2F',
700: '#C62828',
800: '#B71C1C',
900: '#FFCDD2',
},
accent: {
50: '#FFF8E1',
100: '#FFECB3',
200: '#FFE082',
300: '#FFD54F',
400: '#FFCA28',
500: '#FFC107',
600: '#FFB300',
700: '#FFA000',
800: '#FF8F00',
900: '#FF6F00',
},
},
fontFamily: {
heading: ['"Playfair Display"', 'serif'],
body: ['"Inter"', 'sans-serif'],
},
},
},
plugins: [],
};
export default config;
```
- [ ] **Step 4:** Load Google Fonts in `app/layout.tsx`
Edit `app/layout.tsx`:
```tsx
import type { Metadata } from "next";
import { Inter, Playfair_Display } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const playfair = Playfair_Display({
subsets: ["latin"],
variable: "--font-playfair",
});
export const metadata: Metadata = {
title: "Trí Vĩ | AI Engineer",
description: "Personal portfolio of Nguyễn Ngọc Trí Vĩ - AI Engineer",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
- [ ] **Step 5:** Set base styles in `app/globals.css`
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--font-inter: var(--font-inter);
--font-playfair: var(--font-playfair);
}
body {
@apply bg-white text-gray-900;
}
h1, h2, h3, h4, h5, h6 {
@apply font-heading;
}
```
- [ ] **Step 6:** Commit initial setup
```bash
git add .
git commit -m "feat: init Next.js project with Tailwind and fonts"
```
---
### Task 2: Create data layer
**Files:**
- Create: `src/data/profile.ts`
- Create: `src/data/experience.ts`
- Create: `src/data/projects.ts`
- Create: `src/data/blogPosts.ts`
- [ ] **Step 1:** Copy avatar image to `public/avatar.jpeg` (already exists in projects dir)
```bash
cp /home/node/.openclaw/workspace/projects/clawteam-portfolio/avatar.jpeg public/avatar.jpeg
```
- [ ] **Step 2:** Create `src/data/profile.ts`
```ts
export const profile = {
name: "Nguyễn Ngọc Trí Vĩ",
title: "AI Engineer",
avatar: "/avatar.jpeg",
email: "nntrivi2001@gmail.com",
phone: "090 321 5095",
linkedin: "https://linkedin.com/in/nguyen-ngoc-tri-vi",
portfolio: "https://nguyenngoctrivi.vercel.app",
languages: ["Vietnamese (native)", "English (intermediate)"],
bio: `I'm an AI Engineer passionate about applying cutting-edge machine learning to real-world problems. With a strong foundation in Computer Vision and AI research, I've built solutions for industrial quality inspection, trading automation, and educational AI tools. I love exploring new technologies and turning complex ideas into practical, impactful products.`,
};
```
- [ ] **Step 3:** Create `src/data/experience.ts`
```ts
export interface Experience {
id: number;
company: string;
role: string;
startDate: string;
endDate: string;
description: string[];
}
export const experience: Experience[] = [
{
id: 1,
company: "SmartGift Solution",
role: "AI Engineer",
startDate: "Mar 2026",
endDate: "Present",
description: [
"Research and deploy AI assistants to improve development team productivity.",
"Explore and apply suitable technologies for business problems, build POCs, and document practical recommendations.",
],
},
{
id: 2,
company: "Learning Chain Vietnam",
role: "Applied AI Researcher & Operations Associate",
startDate: "Jul 2025",
endDate: "Mar 2026",
description: [
"Research and implement AI for trading automation and operations support.",
"Explore and execute suitable tech, build POCs and document actionable recommendations.",
"Design and teach applied AI training programs, workshops, and n8n templates.",
"Support COO in optimizing workflows, reporting, and cross-team process standardization.",
],
},
{
id: 3,
company: "Vườn đậu - Thương hiệu Sữa đậu nành Thảo mộc",
role: "Founder",
startDate: "Oct 2024",
endDate: "Apr 2025",
description: [
"Managed entire production and business process: sourcing, manufacturing, packaging, distribution, and inventory control.",
"Planned raw material procurement, production schedules, and warehousing to optimize costs and maintain product quality.",
"Developed pricing strategy and direct sales channels. Achieved strong customer satisfaction with average repurchase rate of 2.51x per customer.",
],
},
{
id: 4,
company: "VSTECH Company Limited",
role: "Computer Vision Engineer",
startDate: "Aug 2023",
endDate: "Feb 2025",
description: [
"Nozzle Quality Inspection: Built for Japanese client on production line.",
"3D Vision Integration: Measured Samsung LED panel height with ±7% accuracy.",
"OCR Solutions: Read package codes and text labels with high accuracy in industrial environment.",
],
},
];
```
- [ ] **Step 4:** Create `src/data/projects.ts`
```ts
export interface Project {
id: number;
title: string;
description: string;
tech: string[];
demoUrl?: string;
githubUrl?: string;
image?: string;
}
export const projects: Project[] = [
{
id: 1,
title: "Nozzle Quality Inspection System",
description:
"Computer vision system for automatic nozzle defect detection on production lines. Deployed at Japanese manufacturing facility.",
tech: ["Python", "OpenCV", "TensorFlow", "Industrial Camera"],
demoUrl: "https://nguyenngoctrivi.vercel.app",
githubUrl: "https://github.com/yourusername/nozzle-inspection",
},
{
id: 2,
title: "3D Panel Measurement",
description:
"3D vision integration to measure LED panel height with ±7% accuracy for Samsung.",
tech: ["3D Vision", "Python", "C++", "Point Cloud Processing"],
},
{
id: 3,
title: "OCR for Package Codes",
description:
"High-accuracy OCR for reading package codes and text labels in industrial environments.",
tech: ["OCR", "Python", "OpenCV", "Deep Learning"],
},
{
id: 4,
title: "AI Trading Assistant",
description:
"Research and implementation of AI-driven trading automation and operations support.",
tech: ["Python", "n8n", "PineScript", "Trading APIs"],
},
{
id: 5,
title: "Applied AI Training Program",
description:
"Designed and delivered applied AI training courses, workshops, and learning assets (n8n templates).",
tech: ["AI Education", "n8n", "Workshop Design"],
},
];
```
- [ ] **Step 5:** Create `src/data/blogPosts.ts`
```ts
export interface BlogPost {
slug: string;
title: string;
date: string;
excerpt: string;
content: string;
image?: string;
tags: string[];
}
export const blogPosts: BlogPost[] = [
{
slug: "journey-into-ai",
title: "My Journey into AI",
date: "2025-12-15",
excerpt:
"Reflecting on how I got started with AI, from university days to real-world deployments.",
content: `
# My Journey into AI
It all started at university...
## The Beginning
I was fascinated by how machines could learn...
## First Project
My first computer vision project taught me...
## Lessons Learned
- Start with the fundamentals
- Build real things
- Never stop learning
This journey has been incredible, and I'm excited for what's next.
`.trim(),
tags: ["personal", "ai", "career"],
},
{
slug: "tet-vibes-2025",
title: "Tết 2025 - A Time of Renewal",
date: "2025-01-28",
excerpt:
"Celebrating Lunar New Year with family, food, and wishes for the year ahead.",
content: `
# Tết 2025 - A Time of Renewal
Tết is always a special time...
## Family Reunion
Returning home, sharing meals...
## Traditions
Visiting relatives, lucky money...
## Looking Forward
New year, new goals, new hope.
Chúc mừng năm mới! 🎊
`.trim(),
image: "/images/tet-2025.jpg",
tags: ["personal", "tet", "life"],
},
];
```
- [ ] **Step 6:** Commit data layer
```bash
git add src/data/ public/avatar.jpeg
git commit -m "feat: add data layer (profile, experience, projects, blog)"
```
---
### Task 3: Build TetEffects component (falling petals + subtle fireworks)
**Files:**
- Create: `src/components/TetEffects.tsx`
- [ ] **Step 1:** Create falling petals canvas component
```tsx
"use client";
import { useEffect, useRef } from "react";
interface Petal {
x: number;
y: number;
size: number;
speedY: number;
speedX: number;
rotation: number;
rotationSpeed: number;
opacity: number;
}
export default function TetEffects() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resize();
window.addEventListener("resize", resize);
const petals: Petal[] = [];
const colors = ["#F44336", "#E91E63", "#D32F2F", "#FFC107", "#FFEB3B"]; // reds and golds
const createPetal = () => ({
x: Math.random() * canvas.width,
y: -10,
size: 4 + Math.random() * 8,
speedY: 0.5 + Math.random() * 1.5,
speedX: (Math.random() - 0.5) * 0.5,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.02,
opacity: 0.4 + Math.random() * 0.4,
});
// Initial petals
for (let i = 0; i < 50; i++) {
const p = createPetal();
p.y = Math.random() * canvas.height;
petals.push(p);
}
let animationFrameId: number;
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
petals.forEach((petal, idx) => {
// Update
petal.y += petal.speedY;
petal.x += petal.speedX;
petal.rotation += petal.rotationSpeed;
// Reset if out of bounds
if (petal.y > canvas.height + 10) {
petals[idx] = createPetal();
}
// Draw petal (simple circle or ellipse)
ctx.save();
ctx.translate(petal.x, petal.y);
ctx.rotate(petal.rotation);
ctx.globalAlpha = petal.opacity;
ctx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
ctx.beginPath();
ctx.ellipse(0, 0, petal.size, petal.size / 2, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
animationFrameId = requestAnimationFrame(animate);
};
animate();
return () => {
window.removeEventListener("resize", resize);
cancelAnimationFrame(animationFrameId);
};
}, []);
return (
);
}
```
- [ ] **Step 2:** Add subtle fireworks (sparkles) on idle/scroll
Enhance the same canvas in `TetEffects`:
```tsx
// Inside useEffect after petal animation:
const sparkles: { x: number; y: number; life: number; maxLife: number; size: number }[] = [];
const createSparkle = (x?: number, y?: number) => ({
x: x ?? Math.random() * canvas.width,
y: y ?? Math.random() * canvas.height,
life: 1,
maxLife: 60 + Math.random() * 60,
size: 1 + Math.random() * 2,
});
// Occasionally create sparkles
setInterval(() => {
if (sparkles.length < 20) {
sparkles.push(createSparkle());
}
}, 300);
// In animate loop, after petals:
sparkles.forEach((sparkle, idx) => {
sparkle.life--;
if (sparkle.life <= 0) {
sparkles.splice(idx, 1);
return;
}
ctx.save();
ctx.globalAlpha = sparkle.life / sparkle.maxLife;
ctx.fillStyle = "#FFC107";
ctx.beginPath();
ctx.arc(sparkle.x, sparkle.y, sparkle.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
```
(Integrate into the existing animation loop.)
- [ ] **Step 3:** Commit TetEffects
```bash
git add src/components/TetEffects.tsx
git commit -m "feat: add TetEffects component (falling petals + fireworks)"
```
---
### Task 4: Build common UI components
**Files:**
- Create: `src/components/Header.tsx`
- Create: `src/components/Footer.tsx`
- Create: `src/components/ui/Button.tsx` (optional)
- [ ] **Step 1:** Create `src/components/Header.tsx`
```tsx
"use client";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Link from "next/link";
export default function Header() {
const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 50);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
const navLinks = [
{ href: "#home", label: "Home" },
{ href: "#about", label: "About" },
{ href: "#experience", label: "Experience" },
{ href: "#contact", label: "Contact" },
{ href: "/projects", label: "Projects" },
{ href: "/my-corner", label: "My Corner" },
];
return (
{/* Logo */}
Trí Vĩ
{/* Desktop Nav */}
{/* Mobile menu button */}
{/* Mobile Nav */}
{mobileMenuOpen && (
{navLinks.map((link) => (
setMobileMenuOpen(false)}
>
{link.label}
))}
)}
);
}
```
- [ ] **Step 2:** Create `src/components/Footer.tsx`
```tsx
import Link from "next/link";
export default function Footer() {
return (
);
}
```
- [ ] **Step 3:** Commit Header & Footer
```bash
git add src/components/Header.tsx src/components/Footer.tsx
git commit -m "feat: add Header and Footer components"
```
---
### Task 5: Build Hero section
**Files:**
- Create: `src/components/Hero.tsx`
- [ ] **Step 1:** Create Hero with avatar, name, title, CTA buttons
```tsx
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import { profile } from "@/data/profile";
export default function Hero() {
return (
{/* Falling petals overlay will be rendered at app level */}
);
}
```
- [ ] **Step 2:** Add Avatar image to `public/` (already copied)
- [ ] **Step 3:** Commit Hero
```bash
git add src/components/Hero.tsx
git commit -m "feat: add Hero section component"
```
---
### Task 6: Build About section
**Files:**
- Create: `src/components/About.tsx`
- [ ] **Step 1:** Create About component (2-column layout)
```tsx
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import { profile } from "@/data/profile";
export default function About() {
return (
About Me
{profile.bio}
Languages
{profile.languages.map((lang, idx) => (
- {lang}
))}
);
}
```
- [ ] **Step 2:** Commit About
```bash
git add src/components/About.tsx
git commit -m "feat: add About section component"
```
---
### Task 7: Build Experience (Timeline) component
**Files:**
- Create: `src/components/Experience.tsx`
- [ ] **Step 1:** Create Timeline with vertical line and nodes
```tsx
"use client";
import { motion } from "framer-motion";
import { Experience } from "@/data/experience";
export default function Experience() {
return (
Experience
{/* Vertical line */}
{experience.map((exp, index) => (
{/* Timeline node */}
{/* Content */}
{exp.role}
{exp.company}
{exp.startDate} - {exp.endDate}
{exp.description.map((item, i) => (
- {item}
))}
))}
);
}
```
- [ ] **Step 2:** Commit Experience
```bash
git add src/components/Experience.tsx
git commit -m "feat: add Experience timeline component"
```
---
### Task 8: Build Contact section with form
**Files:**
- Create: `src/components/Contact.tsx`
- [ ] **Step 1:** Create Contact form (no backend, just UI + success animation)
```tsx
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { profile } from "@/data/profile";
export default function Contact() {
const [submitted, setSubmitted] = useState(false);
const [formData, setFormData] = useState({
name: "",
email: "",
message: "",
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Simulate submission
setSubmitted(true);
setTimeout(() => {
setSubmitted(false);
setFormData({ name: "", email: "", message: "" });
}, 3000);
};
return (
);
}
```
- [ ] **Step 2:** Commit Contact
```bash
git add src/components/Contact.tsx
git commit -m "feat: add Contact section with form and success animation"
```
---
### Task 9: Build ProjectCard component
**Files:**
- Create: `src/components/ProjectCard.tsx`
- [ ] **Step 1:** Create ProjectCard
```tsx
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import { Project } from "@/data/projects";
export default function ProjectCard({ project }: { project: Project }) {
return (
{project.image && (
)}
{project.title}
{project.description}
{project.tech.map((t) => (
{t}
))}
);
}
```
- [ ] **Step 2:** Commit ProjectCard
```bash
git add src/components/ProjectCard.tsx
git commit -m "feat: add ProjectCard component"
```
---
### Task 10: Build BlogCard component
**Files:**
- Create: `src/components/BlogCard.tsx`
- [ ] **Step 1:** Create BlogCard
```tsx
import Link from "next/link";
import { BlogPost } from "@/data/blogPosts";
export default function BlogCard({ post }: { post: BlogPost }) {
return (
{post.image && (
)}
{post.date}
{post.title}
{post.excerpt}
{post.tags.map((tag) => (
#{tag}
))}
Read more →
);
}
```
- [ ] **Step 2:** Commit BlogCard
```bash
git add src/components/BlogCard.tsx
git commit -m "feat: add BlogCard component"
```
---
### Task 11: Compose Home page
**Files:**
- Modify: `app/page.tsx` (replace default)
- Modify: `app/layout.tsx` (to include TetEffects and Header/Footer)
- [ ] **Step 1:** Update `app/layout.tsx` to include global wrappers
```tsx
// Add imports
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import TetEffects from "@/components/TetEffects";
// Inside RootLayout, wrap children:
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
- [ ] **Step 2:** Rewrite `app/page.tsx` to assemble Home sections
```tsx
import Hero from "@/components/Hero";
import About from "@/components/About";
import Experience from "@/components/Experience";
import Contact from "@/components/Contact";
export default function Home() {
return (
<>
>
);
}
```
- [ ] **Step 3:** Commit Home assembly
```bash
git add app/layout.tsx app/page.tsx
git commit -m "feat: compose Home page with all sections"
```
---
### Task 12: Build Projects page
**Files:**
- Create: `app/projects/page.tsx`
- [ ] **Step 1:** Create Projects page with grid
```tsx
import { motion } from "framer-motion";
import { projects } from "@/data/projects";
import ProjectCard from "@/components/ProjectCard";
export default function ProjectsPage() {
return (
Projects
{projects.map((project) => (
))}
);
}
```
- [ ] **Step 2:** Commit Projects page
```bash
git add app/projects/page.tsx
git commit -m "feat: add Projects page"
```
---
### Task 13: Build My Corner page (blog list)
**Files:**
- Create: `app/my-corner/page.tsx`
- [ ] **Step 1:** Create My Corner list page
```tsx
import { motion } from "framer-motion";
import { blogPosts } from "@/data/blogPosts";
import BlogCard from "@/components/BlogCard";
export default function MyCornerPage() {
return (
My Corner
Personal stories, memories, and reflections
{blogPosts.map((post) => (
))}
);
}
```
- [ ] **Step 2:** Commit My Corner page
```bash
git add app/my-corner/page.tsx
git commit -m "feat: add My Corner blog list page"
```
---
### Task 14: Build single blog post page
**Files:**
- Create: `app/my-corner/[slug]/page.tsx`
- [ ] **Step 1:** Create dynamic route with markdown rendering (or plain HTML for simplicity; we can add remark later)
```tsx
import { notFound } from "next/navigation";
import { blogPosts } from "@/data/blogPosts";
import Link from "next/link";
import { motion } from "framer-motion";
export function generateStaticParams() {
return blogPosts.map((post) => ({
slug: post.slug,
}));
}
export default function BlogPostPage({
params,
}: {
params: { slug: string };
}) {
const post = blogPosts.find((p) => p.slug === params.slug);
if (!post) {
notFound();
}
return (
← Back to My Corner
{post.title}
{post.date}
{post.image && (
)}
{/* Simple line-based rendering; for richer markdown, add 'react-markdown' */}
{post.content.split("\n").map((line, i) => {
if (line.startsWith("# ")) {
return (
{line.slice(2)}
);
}
if (line.startsWith("## ")) {
return (
{line.slice(3)}
);
}
if (line.startsWith("- ")) {
return (
{line.slice(2)}
);
}
if (line.trim() === "") {
return
;
}
return (
{line}
);
})}
);
}
```
- [ ] **Step 2:** Commit dynamic blog page
```bash
git add app/my-corner/[slug]/page.tsx
git commit -m "feat: add dynamic blog post page"
```
---
### Task 15: Add scroll animations to Home sections
**Files:**
- `src/components/Hero.tsx` (already has Framer Motion)
- `src/components/About.tsx`
- `src/components/Experience.tsx`
- `src/components/Contact.tsx`
- `app/page.tsx` (wrap sections in motion.div with viewport)
- [ ] **Step 1:** Wrap each section in `motion.section` with `whileInView` for scroll reveal (already added in About, Experience, Contact from previous tasks). Verify smooth behavior.
- [ ] **Step 2:** Add stagger to timeline items in Experience (already in loop with delay)
- [ ] **Step 3:** Ensure Hero animates on load (already done)
- [ ] **Step 4:** Commit scroll animations
```bash
git add src/components/Hero.tsx src/components/About.tsx src/components/Experience.tsx src/components/Contact.tsx
git commit -m "feat: add scroll-triggered animations with Framer Motion"
```
---
### Task 16: Responsive styling and mobile menu
**Files:**
- `src/components/Header.tsx` (already responsive)
- All pages and components use Tailwind responsive classes
- [ ] **Step 1:** Verify Header mobile menu works on small screens (already built)
- [ ] **Step 2:** Ensure Home sections stack properly on mobile (Tailwind responsive classes already used)
- [ ] **Step 3:** Verify Projects grid: `sm:grid-cols-2 lg:grid-cols-3` (already set)
- [ ] **Step 4:** Test on multiple viewports mentally or suggest manual testing
- [ ] **Step 5:** Commit responsive pass
```bash
git add .
git commit -m "style: ensure responsive layouts across all pages"
```
---
### Task 17: Test build locally
**Files:**
- All project files
- [ ] **Step 1:** Run development server
```bash
npm run dev
```
Check for console errors, broken routes, missing images.
- [ ] **Step 2:** Test each route:
- `/`
- `/projects`
- `/my-corner`
- `/my-corner/journey-into-ai` (example)
- [ ] **Step 3:** Test contact form submission (mock)
- [ ] **Step 4:** Verify animations smooth (60fps expected)
- [ ] **Step 5:** Fix any issues found
- [ ] **Step 6:** Build for production
```bash
npm run build
```
Ensure build completes without errors.
- [ ] **Step 7:** Commit final fixes
```bash
git add .
git commit -m "fix: address build/test issues and finalize"
```
---
### Task 18: Prepare for Vercel deployment
**Files:**
- Optionally create `vercel.json` if custom config needed (usually not)
- Update `README.md` with deployment instructions
- [ ] **Step 1:** Create README.md
```markdown
# Trí Vĩ Portfolio
Personal portfolio built with Next.js 14, Tailwind CSS, Framer Motion, featuring a traditional Tết theme.
## Features
- Responsive design (mobile, tablet, desktop)
- Smooth animations with Framer Motion
- Traditional Tết theme (falling petals, fireworks, red/gold colors)
- Multi-page routing: Home, Projects, My Corner (blog)
- Contact form with success animation
## Tech Stack
- Next.js 14 (App Router)
- TypeScript
- Tailwind CSS
- Framer Motion
## Getting Started
1. Clone the repository
2. Install dependencies: \`npm install\`
3. Run the development server: \`npm run dev\`
4. Open [http://localhost:3000](http://localhost:3000) in your browser
## Build for Production
\`\`\`bash
npm run build
npm start
\`\`\`
## Deploy on Vercel
[Import this repository on Vercel](https://vercel.com/new) and deploy automatically.
```
- [ ] **Step 2:** Commit README
```bash
git add README.md
git commit -m "docs: add README with deploy instructions"
```
- [ ] **Step 3:** Push to remote (if configured) or instruct user to deploy via Vercel CLI
---
### Acceptance Checklist
- [ ] Next.js project initializes and runs locally
- [ ] Tailwind colors configured (primary red, accent gold)
- [ ] Google Fonts loaded (Playfair Display, Inter)
- [ ] Header responsive (desktop nav + mobile hamburger)
- [ ] Hero section with avatar, gradient, CTA buttons
- [ ] About section with bio, languages, contact info
- [ ] Experience timeline with 4 positions, alternating sides, on-scroll animations
- [ ] Contact form with fields (name, email, message) and success modal
- [ ] TetEffects component: falling petals canvas + subtle sparkles
- [ ] Projects page with grid of project cards (hover effects, tech badges)
- [ ] My Corner page with blog cards list
- [ ] Blog post dynamic page renders content
- [ ] Scroll animations on all Home sections (fade/slide)
- [ ] Responsive on mobile (<768px), tablet (768-1024px), desktop (>1024px)
- [ ] No console errors or TypeScript warnings
- [ ] Production build succeeds (`npm run build`)
- [ ] README with instructions
- [ ] Ready for Vercel deployment
---
**Spec referenced:** `docs/superpowers/specs/2026-03-31-portfolio-tet-theme-design.md`
**Total estimated tasks:** ~18 major tasks, broken into 2-5 minute steps each. With ClawTeam parallelization, this can be completed quickly.
**Notes for implementer:**
- Use `clawteam spawn` to assign tasks to workers.
- Each worker should get one component/page at a time.
- Review between tasks (two-stage code review).
- Keep commits small and descriptive.
- Ensure data is accurately transferred from CV.
- Polish animations to be smooth (60fps).
- Test responsiveness thoroughly.
- Keep the Tết theme elegant and not overwhelming.
- Do not simplify; deliver a complete, polished product ready to deploy.