## Identity

- Name: hero
- ID: 2736126a416e
- Type: general-purpose
- Team: portfolio-tet-2026
- Leader: sagi

## Workspace
- Working directory: /home/node/.clawteam/workspaces/portfolio-tet-2026/hero
- Branch: clawteam/portfolio-tet-2026/hero
- This is an isolated git worktree. Your changes do not affect the main branch.

## Task

Task 5: Build Hero section with avatar, gradient, CTAs. Follow implementation-plan.md.

## Context

# Git Context for hero
## Upstream Dependency Changes
- data: data: 0 file(s), +0/-0, 0 commit(s)
- init: init: 0 file(s), +0/-0, 0 commit(s)

## Coordination Protocol

- Use `clawteam task list portfolio-tet-2026 --owner hero` to see your tasks.
- Starting a task: `clawteam task update portfolio-tet-2026 <task-id> --status in_progress`
- Before marking a task completed, commit your changes in this repository with git.
- Use a clear commit message, e.g. `git add -A && git commit -m "Implement <task summary>"`.
- Finishing a task: `clawteam task update portfolio-tet-2026 <task-id> --status completed`
- When you finish all tasks, send a summary to the leader:
  `clawteam inbox send portfolio-tet-2026 sagi "All tasks completed. <brief summary>"`
- If you are blocked or need help, message the leader:
  `clawteam inbox send portfolio-tet-2026 sagi "Need help: <description>"`
- After finishing work, report your costs: `clawteam cost report portfolio-tet-2026 --input-tokens <N> --output-tokens <N> --cost-cents <N>`
- Before finishing, save your session: `clawteam session save portfolio-tet-2026 --session-id <id>`
- Do not exit after the first task unless the leader explicitly tells you to stop.

## Worker Loop Protocol

- After finishing your current task batch, re-check `clawteam task list portfolio-tet-2026 --owner hero`.
- Then check for new instructions with `clawteam inbox receive portfolio-tet-2026 --agent hero`.
- If you become idle, notify the leader with `clawteam lifecycle idle portfolio-tet-2026` and continue checking for new work.
- Repeat this loop until the leader confirms shutdown or there is truly no more work to do.


I'm an agent named "hero" working on a portfolio project for team "portfolio-tet-2026" led by "sagi". My task is to build a Hero section with avatar, gradient, and CTAs, following an implementation-plan.md.

Let me start by understanding the workspace and the implementation plan:

1. I'm in an isolated git worktree at `/home/node/.clawteam/workspaces/portfolio-tet-2026/hero`
2. I need to check the implementation plan: `implementation-plan.md`
3. Check the current task status with `clawteam task list portfolio-tet-2026 --owner hero`
4. Follow the coordination protocol

Let me first explore the workspace and read the implementation plan to understand exactly what needs to be built.

I'll start by understanding the current workspace and reading the implementation plan.


ENOENT: no such file or directory, access '/home/node/.openclaw/workspace/implementation-plan.md'


Tasks - portfolio-tet-2026
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━┓
┃ ID       ┃ Subject       ┃ Status  ┃ Priority ┃ Owner ┃ Lock ┃ Blocked By    ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━┩
│ c5f11815 │ Build Hero    │ blocked │ medium   │ hero  │      │ 685f1607,     │
│          │ section       │         │          │       │      │ 5b75def0      │
└──────────┴───────────────┴─────────┴──────────┴───────┴──────┴───────────────┘


total 1988
drwxrwxr-x 10 node node    4096 Mar 31 03:19 .
drwx------ 15 node node    4096 Mar 31 01:24 ..
drwxr-xr-x  2 node node    4096 Mar 25 08:46 .clawhub
drwxr-xr-x  8 node node    4096 Mar 31 03:19 .git
drwxr-xr-x  2 node node    4096 Mar 31 02:17 .learnings
drwxr-xr-x  2 node node    4096 Mar 24 06:52 .openclaw
-rw-r--r--  1 node node    9073 Mar 26 06:33 AGENTS.md
-rw-r--r--  1 node node    1471 Mar 24 06:52 BOOTSTRAP.md
-rw-r--r--  1 node node     193 Mar 24 06:52 HEARTBEAT.md
-rw-r--r--  1 node node     733 Mar 26 07:57 IDENTITY.md
-rw-rw-r--  1 node node    3936 Mar 30 09:24 NguyenNgocTriVi_CV.md
-rw-rw-r--  1 node node    4481 Mar 31 01:50 RULES.md
-rw-r--r--  1 node node    1673 Mar 24 06:52 SOUL.md
-rw-r--r--  1 node node    1776 Mar 26 04:38 TOOLS.md
-rw-r--r--  1 node node     502 Mar 26 07:57 USER.md
-rw-rw-r--  1 node node  186261 Mar 30 09:48 avatar.jpeg
drwxr-xr-x  3 node node    4096 Mar 26 06:01 docs
drwxr-xr-x  2 node node    4096 Mar 31 02:28 memory
drwxr-xr-x  4 node node    4096 Mar 30 09:12 projects
-rw-rw-r--  1 node node 1717169 Mar 26 07:53 sagi_avatar.png
drwxr-xr-x 18 node node    4096 Mar 30 10:01 skills


The implementation plan file doesn't exist in the workspace. It's likely in the parent directory or somewhere else. Let me check the parent directory structure. Also, the task is blocked by two other tasks: 685f1607 and 5b75def0.

Let me see if I can find the implementation plan file and understand the task dependencies.

Let me check the parent workspace for the implementation plan and understand the blocked dependencies:

cd /home/node/.clawteam/workspaces/portfolio-tet-2026 && ls -la


total 84
drwxr-xr-x 19 node node 4096 Mar 31 03:20 .
drwxr-xr-x  3 node node 4096 Mar 31 02:55 ..
drwxr-xr-x  2 node node 4096 Mar 31 03:20 about
drwxr-xr-x  2 node node 4096 Mar 31 03:06 blog-card
drwxr-xr-x  2 node node 4096 Mar 31 03:07 blog-dynamic
drwxr-xr-x  2 node node 4096 Mar 31 03:07 build-deploy
drwxr-xr-x  2 node node 4096 Mar 31 03:20 contact
drwxr-xr-x  2 node node 4096 Mar 31 03:19 data
drwxr-xr-x  2 node node 4096 Mar 31 03:20 experience
drwxr-xr-x  2 node node 4096 Mar 31 03:19 header-footer
drwxr-xr-x  2 node node 4096 Mar 31 03:20 hero
drwxr-xr-x  2 node node 4096 Mar 31 03:06 home-compose
drwxr-xr-x  2 node node 4096 Mar 31 03:18 init
drwxr-xr-x  2 node node 4096 Mar 31 03:06 mycorner-page
drwxr-xr-x  2 node node 4096 Mar 31 03:07 polish
drwxr-xr-x  2 node node 4096 Mar 31 03:20 project-card
drwxr-xr-x  2 node node 4096 Mar 31 03:06 projects-page
drwxr-xr-x  2 node node 4096 Mar 31 03:20 test-agent
drwxr-xr-x  2 node node 4096 Mar 31 03:19 tet-effects
-rw-r--r--  1 node node 7579 Mar 31 03:20 workspace-registry.json


Tasks - portfolio-tet-2026
┏━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━┓
┃          ┃          ┃          ┃          ┃           ┃          ┃ Blocked   ┃
┃ ID       ┃ Subject  ┃ Status   ┃ Priority ┃ Owner     ┃ Lock     ┃ By        ┃
┡━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━┩
│ 24d73617 │ Test     │ pending  │ high     │ build-de… │          │           │
│          │ build &  │          │          │           │          │           │
│          │ prepare  │          │          │           │          │           │
│          │ deploym… │          │          │           │          │           │
│ 2599ccbe │ Polish   │ blocked  │ medium   │ polish    │          │ 63b353b0, │
│          │ animati… │          │          │           │          │ 71db9f4d, │
│          │ &        │          │          │           │          │ 3c2967eb, │
│          │ respons… │          │          │           │          │ 4664aa4e  │
│ 393fb6a3 │ Test     │ in_prog… │ low      │ test-age… │ test-ag… │           │
│          │ task     │          │          │           │          │           │
│ 3c2967eb │ Build My │ blocked  │ medium   │ mycorner… │          │ 631d76b4  │
│          │ Corner   │          │          │           │          │           │
│          │ page     │          │          │           │          │           │
│ 4664aa4e │ Build    │ blocked  │ medium   │ blog-dyn… │          │ 631d76b4, │
│          │ blog     │          │          │           │          │ 5b75def0  │
│          │ dynamic  │          │          │           │          │           │
│          │ page     │          │          │           │          │           │
│ 5b75def0 │ Create   │ blocked  │ high     │ data      │          │ 685f1607  │
│          │ data     │          │          │           │          │           │
│          │ layer    │          │          │           │          │           │
│ 631d76b4 │ Build    │ blocked  │ medium   │ blog-card │          │ 685f1607, │
│          │ BlogCard │          │          │           │          │ 5b75def0  │
│          │ compone… │          │          │           │          │           │
│ 63b353b0 │ Compose  │ blocked  │ medium   │ home-com… │          │ c5f11815, │
│          │ Home     │          │          │           │          │ f0406392, │
│          │ page     │          │          │           │          │ 87c38bb6, │
│          │          │          │          │           │          │ bebc7e6f  │
│ 685f1607 │ Initial… │ in_prog… │ high     │ init      │ init     │           │
│          │ Next.js  │          │          │           │          │           │
│          │ project  │          │          │           │          │           │
│ 6a4bbfbb │ Initial… │ pending  │ high     │ unused    │          │           │
│          │ Next.js  │          │          │           │          │           │
│          │ project  │          │          │           │          │           │
│ 71db9f4d │ Build    │ blocked  │ medium   │ projects… │          │ 89ddfb77  │
│          │ Projects │          │          │           │          │           │
│          │ page     │          │          │           │          │           │
│ 87c38bb6 │ Build    │ blocked  │ medium   │ experien… │          │ 685f1607, │
│          │ Experie… │          │          │           │          │ 5b75def0  │
│          │ timeline │          │          │           │          │           │
│ 89ddfb77 │ Build    │ blocked  │ medium   │ project-… │          │ 685f1607, │
│          │ Project… │          │          │           │          │ 5b75def0  │
│          │ compone… │          │          │           │          │           │
│ bebc7e6f │ Build    │ blocked  │ medium   │ contact   │          │ 685f1607, │
│          │ Contact  │          │          │           │          │ 5b75def0  │
│          │ section  │          │          │           │          │           │
│ c5f11815 │ Build    │ blocked  │ medium   │ hero      │          │ 685f1607, │
│          │ Hero     │          │          │           │          │ 5b75def0  │
│          │ section  │          │          │           │          │           │
│ ccd4055d │ Build    │ blocked  │ medium   │ tet-effe… │          │ 685f1607  │
│          │ TetEffe… │          │          │           │          │           │
│          │ compone… │          │          │           │          │           │
│ f0406392 │ Build    │ blocked  │ medium   │ about     │          │ 685f1607, │
│          │ About    │          │          │           │          │ 5b75def0  │
│          │ section  │          │          │           │          │           │
│ fbb0da4a │ Build    │ blocked  │ medium   │ header-f… │          │ 685f1607  │
│          │ Header & │          │          │           │          │           │
│          │ Footer   │          │          │           │          │           │
└──────────┴──────────┴──────────┴──────────┴───────────┴──────────┴───────────┘


I can see that:
1. My task (c5f11815: Build Hero section) is blocked by two tasks: 685f1607 and 5b75def0
2. 685f1607 is "Initial Setup Next.js project" which is currently in_progress and owned by init
3. 5b75def0 is "Create data layer" which is blocked by 685f1607

So the dependency chain is:
- Hero section depends on Initial Setup and Data layer
- Initial Setup is in progress by init agent
- Data layer is blocked by Initial Setup

I should wait for the initial setup to complete before I can work on the hero section. But maybe I can check the hero workspace to see if there's any existing implementation plan or README that tells me what to build.

Let me check if there's an implementation plan in the parent directory and also check the hero workspace contents.

find /home/node/.clawteam/workspaces/portfolio-tet-2026 -name "implementation-plan.md" -o -name "README.md" -o -name "plan.md" 2>/dev/null


Found it! The implementation plan for hero is at `/home/node/.clawteam/workspaces/portfolio-tet-2026/hero/implementation-plan.md`. Let me read it.

Now let me read the implementation plan for the Hero section:

clawteam inbox receive portfolio-tet-2026 --agent hero


# 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 (
    <html lang="en">
      <body className={`${inter.variable} ${playfair.variable} font-body`}>
        {children}
      </body>
    </html>
  );
}
```

- [ ] **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<HTMLCanvasElement>(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 (
    <canvas
      ref={canvasRef}
      className="fixed inset-0 pointer-events-none z-50"
      style={{ opacity: 0.6 }}
    />
  );
}
```

- [ ] **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 (
    <header
      className={`fixed top-0 left-0 right-0 z-40 transition-all duration-300 ${
        scrolled
          ? "bg-white/80 backdrop-blur-md shadow-sm"
          : "bg-transparent"
      }`}
    >
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between items-center h-16">
          {/* Logo */}
          <Link href="/" className="text-2xl font-bold text-primary-600">
            Trí Vĩ
          </Link>

          {/* Desktop Nav */}
          <nav className="hidden md:flex space-x-8">
            {navLinks.map((link) => (
              <Link
                key={link.href}
                href={link.href}
                className="text-gray-700 hover:text-primary-600 transition-colors font-medium"
              >
                {link.label}
              </Link>
            ))}
          </nav>

          {/* Mobile menu button */}
          <button
            className="md:hidden p-2"
            onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
            aria-label="Toggle menu"
          >
            <svg
              className="w-6 h-6"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              {mobileMenuOpen ? (
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M6 18L18 6M6 6l12 12"
                />
              ) : (
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M4 6h16M4 12h16M4 18h16"
                />
              )}
            </svg>
          </button>
        </div>
      </div>

      {/* Mobile Nav */}
      <AnimatePresence>
        {mobileMenuOpen && (
          <motion.div
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: "auto" }}
            exit={{ opacity: 0, height: 0 }}
            className="md:hidden bg-white shadow-lg"
          >
            <div className="px-4 py-4 space-y-2">
              {navLinks.map((link) => (
                <Link
                  key={link.href}
                  href={link.href}
                  className="block py-2 text-gray-700 hover:text-primary-600"
                  onClick={() => setMobileMenuOpen(false)}
                >
                  {link.label}
                </Link>
              ))}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </header>
  );
}
```

- [ ] **Step 2:** Create `src/components/Footer.tsx`

```tsx
import Link from "next/link";

export default function Footer() {
  return (
    <footer className="bg-gradient-to-r from-primary-600 to-primary-800 text-white py-12">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex flex-col md:flex-row justify-between items-center">
          <div className="mb-4 md:mb-0">
            <h3 className="text-2xl font-bold mb-2">Trí Vĩ</h3>
            <p className="text-gray-200">AI Engineer</p>
          </div>
          <div className="text-sm">
            © 2025 - 2026 Nguyễn Ngọc Trí Vĩ. All rights reserved.
          </div>
        </div>
        {/* Decorative lion/dragon silhouette could be added here */}
      </div>
    </footer>
  );
}
```

- [ ] **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 (
    <section
      id="home"
      className="relative min-h-screen flex items-center justify-center bg-gradient-to-br from-primary-500 via-accent-400 to-primary-600 overflow-hidden"
    >
      {/* Falling petals overlay will be rendered at app level */}

      <div className="relative z-10 text-center px-4 max-w-4xl mx-auto">
        <motion.div
          initial={{ scale: 0.8, opacity: 0 }}
          animate={{ scale: 1, opacity: 1 }}
          transition={{ duration: 0.8 }}
          className="mb-8"
        >
          <div className="relative w-48 h-48 mx-auto rounded-full overflow-hidden border-4 border-white shadow-2xl">
            <Image
              src={profile.avatar}
              alt={profile.name}
              fill
              className="object-cover"
              priority
            />
          </div>
        </motion.div>

        <motion.h1
          initial={{ y: 30, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.2 }}
          className="text-5xl md:text-6xl font-heading font-bold text-white mb-4"
        >
          {profile.name}
        </motion.h1>

        <motion.p
          initial={{ y: 30, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.4 }}
          className="text-2xl md:text-3xl text-gray-100 mb-8"
        >
          {profile.title}
        </motion.p>

        <motion.div
          initial={{ y: 30, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          transition={{ delay: 0.6 }}
          className="flex flex-col sm:flex-row gap-4 justify-center"
        >
          <a
            href="/projects"
            className="px-8 py-3 bg-white text-primary-600 rounded-full font-semibold shadow-lg hover:shadow-xl hover:bg-gray-50 transition-all"
          >
            View Projects
          </a>
          <a
            href="#contact"
            className="px-8 py-3 border-2 border-white text-white rounded-full font-semibold hover:bg-white hover:text-primary-600 transition-all"
          >
            Contact Me
          </a>
        </motion.div>
      </div>
    </section>
  );
}
```

- [ ] **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 (
    <section id="about" className="py-20 bg-white">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <motion.div
          initial={{ opacity: 0 }}
          whileInView={{ opacity: 1 }}
          viewport={{ once: true }}
          transition={{ duration: 0.6 }}
          className="text-center mb-12"
        >
          <h2 className="text-4xl font-heading font-bold text-primary-700 mb-4">
            About Me
          </h2>
          <div className="w-24 h-1 bg-accent-500 mx-auto" />
        </motion.div>

        <div className="grid md:grid-cols-2 gap-12 items-center">
          <motion.div
            initial={{ x: -50, opacity: 0 }}
            whileInView={{ x: 0, opacity: 1 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6 }}
            className="flex justify-center"
          >
            <div className="relative w-80 h-80 rounded-2xl overflow-hidden shadow-2xl">
              <Image
                src={profile.avatar}
                alt={profile.name}
                fill
                className="object-cover"
              />
            </div>
          </motion.div>

          <motion.div
            initial={{ x: 50, opacity: 0 }}
            whileInView={{ x: 0, opacity: 1 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6 }}
            className="space-y-6"
          >
            <p className="text-lg text-gray-700 leading-relaxed">
              {profile.bio}
            </p>

            <div>
              <h3 className="text-xl font-semibold text-primary-600 mb-2">
                Languages
              </h3>
              <ul className="list-disc list-inside text-gray-700 space-y-1">
                {profile.languages.map((lang, idx) => (
                  <li key={idx}>{lang}</li>
                ))}
              </ul>
            </div>

            <div>
              <h3 className="text-xl font-semibold text-primary-600 mb-2">
                Contact
              </h3>
              <div className="space-y-2 text-gray-700">
                <p>
                  <strong>Email:</strong>{" "}
                  <a
                    href={`mailto:${profile.email}`}
                    className="text-primary-600 hover:underline"
                  >
                    {profile.email}
                  </a>
                </p>
                <p>
                  <strong>Phone:</strong> {profile.phone}
                </p>
                <p>
                  <strong>LinkedIn:</strong>{" "}
                  <a
                    href={profile.linkedin}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-primary-600 hover:underline"
                  >
                    {profile.linkedin}
                  </a>
                </p>
              </div>
            </div>
          </motion.div>
        </div>
      </div>
    </section>
  );
}
```

- [ ] **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 (
    <section id="experience" className="py-20 bg-gray-50">
      <div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
        <motion.div
          initial={{ opacity: 0 }}
          whileInView={{ opacity: 1 }}
          viewport={{ once: true }}
          transition={{ duration: 0.6 }}
          className="text-center mb-16"
        >
          <h2 className="text-4xl font-heading font-bold text-primary-700 mb-4">
            Experience
          </h2>
          <div className="w-24 h-1 bg-accent-500 mx-auto" />
        </motion.div>

        <div className="relative">
          {/* Vertical line */}
          <div className="absolute left-8 md:left-1/2 top-0 bottom-0 w-0.5 bg-primary-300 transform md:-translate-x-1/2" />

          {experience.map((exp, index) => (
            <motion.div
              key={exp.id}
              initial={{ opacity: 0, x: index % 2 === 0 ? -30 : 30 }}
              whileInView={{ opacity: 1, x: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.6, delay: index * 0.1 }}
              className={`relative flex items-center mb-12 ${
                index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse"
              }`}
            >
              {/* Timeline node */}
              <div className="absolute left-6 md:left-1/2 w-5 h-5 bg-accent-500 rounded-full border-4 border-white shadow-lg transform -translate-x-1/2 z-10" />

              {/* Content */}
              <div className={`ml-16 md:ml-0 md:w-1/2 ${index % 2 === 0 ? "md:pr-12" : "md:pl-12"}`}>
                <div className="bg-white p-6 rounded-xl shadow-lg">
                  <h3 className="text-2xl font-bold text-primary-700">
                    {exp.role}
                  </h3>
                  <p className="text-lg text-accent-600 font-medium mb-1">
                    {exp.company}
                  </p>
                  <p className="text-sm text-gray-500 mb-4">
                    {exp.startDate} - {exp.endDate}
                  </p>
                  <ul className="list-disc list-inside text-gray-700 space-y-2">
                    {exp.description.map((item, i) => (
                      <li key={i}>{item}</li>
                    ))}
                  </ul>
                </div>
              </div>
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}
```

- [ ] **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 (
    <section id="contact" className="py-20 bg-white">
      <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
        <motion.div
          initial={{ opacity: 0 }}
          whileInView={{ opacity: 1 }}
          viewport={{ once: true }}
          transition={{ duration: 0.6 }}
          className="text-center mb-12"
        >
          <h2 className="text-4xl font-heading font-bold text-primary-700 mb-4">
            Get In Touch
          </h2>
          <div className="w-24 h-1 bg-accent-500 mx-auto" />
        </motion.div>

        <div className="grid md:grid-cols-2 gap-12">
          {/* Contact Info */}
          <motion.div
            initial={{ x: -30, opacity: 0 }}
            whileInView={{ x: 0, opacity: 1 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6 }}
            className="space-y-6"
          >
            <div>
              <h3 className="text-xl font-semibold text-primary-600 mb-2">
                Contact Information
              </h3>
              <div className="space-y-4">
                <p>
                  <strong>Email:</strong>
                  <br />
                  <a
                    href={`mailto:${profile.email}`}
                    className="text-primary-600 hover:underline"
                  >
                    {profile.email}
                  </a>
                </p>
                <p>
                  <strong>Phone:</strong>
                  <br />
                  {profile.phone}
                </p>
                <p>
                  <strong>LinkedIn:</strong>
                  <br />
                  <a
                    href={profile.linkedin}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-primary-600 hover:underline"
                  >
                    {profile.linkedin}
                  </a>
                </p>
              </div>
            </div>

            <div className="pt-8 border-t border-gray-200">
              <p className="text-gray-600 italic">
                I'd love to hear from you! Whether you have a project in mind,
                want to collaborate, or just want to say hi, feel free to reach
                out.
              </p>
            </div>
          </motion.div>

          {/* Form */}
          <motion.div
            initial={{ x: 30, opacity: 0 }}
            whileInView={{ x: 0, opacity: 1 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6 }}
          >
            <form onSubmit={handleSubmit} className="space-y-6">
              <div>
                <label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
                  Name
                </label>
                <input
                  type="text"
                  id="name"
                  required
                  value={formData.name}
                  onChange={(e) =>
                    setFormData({ ...formData, name: e.target.value })
                  }
                  className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition"
                  placeholder="Your name"
                />
              </div>

              <div>
                <label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
                  Email
                </label>
                <input
                  type="email"
                  id="email"
                  required
                  value={formData.email}
                  onChange={(e) =>
                    setFormData({ ...formData, email: e.target.value })
                  }
                  className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition"
                  placeholder="your@email.com"
                />
              </div>

              <div>
                <label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-1">
                  Message
                </label>
                <textarea
                  id="message"
                  rows={5}
                  required
                  value={formData.message}
                  onChange={(e) =>
                    setFormData({ ...formData, message: e.target.value })
                  }
                  className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition resize-none"
                  placeholder="Your message..."
                />
              </div>

              <button
                type="submit"
                className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 transition-all shadow-md hover:shadow-lg"
              >
                Send Message
              </button>
            </form>

            {/* Success confetti animation */}
            <AnimatePresence>
              {submitted && (
                <motion.div
                  initial={{ scale: 0, opacity: 0 }}
                  animate={{ scale: 1, opacity: 1 }}
                  exit={{ scale: 0, opacity: 0 }}
                  className="fixed inset-0 flex items-center justify-center bg-black/50 z-50"
                >
                  <motion.div
                    initial={{ y: 50, opacity: 0 }}
                    animate={{ y: 0, opacity: 1 }}
                    transition={{ delay: 0.2 }}
                    className="bg-white p-8 rounded-2xl shadow-2xl text-center"
                  >
                    <h3 className="text-2xl font-bold text-primary-600 mb-2">
                      Message Sent!
                    </h3>
                    <p className="text-gray-600">
                      Thanks for reaching out. I'll get back to you soon.
                    </p>
                  </motion.div>
                </motion.div>
              )}
            </AnimatePresence>
          </motion.div>
        </div>
      </div>
    </section>
  );
}
```

- [ ] **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 (
    <motion.article
      whileHover={{ y: -8, scale: 1.02 }}
      className="bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-100 hover:border-primary-200 transition-all"
    >
      {project.image && (
        <div className="relative h-48 w-full">
          <Image
            src={project.image}
            alt={project.title}
            fill
            className="object-cover"
          />
        </div>
      )}
      <div className="p-6">
        <h3 className="text-xl font-bold text-primary-700 mb-2">
          {project.title}
        </h3>
        <p className="text-gray-600 mb-4">{project.description}</p>

        <div className="flex flex-wrap gap-2 mb-4">
          {project.tech.map((t) => (
            <span
              key={t}
              className="px-3 py-1 bg-accent-100 text-accent-800 rounded-full text-sm font-medium"
            >
              {t}
            </span>
          ))}
        </div>

        <div className="flex gap-4">
          {project.demoUrl && (
            <a
              href={project.demoUrl}
              target="_blank"
              rel="noopener noreferrer"
              className="text-primary-600 hover:text-primary-800 font-medium"
            >
              Live Demo →
            </a>
          )}
          {project.githubUrl && (
            <a
              href={project.githubUrl}
              target="_blank"
              rel="noopener noreferrer"
              className="text-gray-700 hover:text-gray-900 font-medium"
            >
              GitHub →
            </a>
          )}
        </div>
      </div>
    </motion.article>
  );
}
```

- [ ] **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 (
    <article className="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-xl transition-shadow border border-gray-100">
      {post.image && (
        <div className="relative h-48 w-full">
          <img
            src={post.image}
            alt={post.title}
            className="object-cover w-full h-full"
          />
        </div>
      )}
      <div className="p-6">
        <p className="text-sm text-gray-500 mb-2">{post.date}</p>
        <h3 className="text-xl font-bold text-primary-700 mb-2">
          {post.title}
        </h3>
        <p className="text-gray-600 mb-4">{post.excerpt}</p>
        <div className="flex flex-wrap gap-2 mb-4">
          {post.tags.map((tag) => (
            <span
              key={tag}
              className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-xs font-medium"
            >
              #{tag}
            </span>
          ))}
        </div>
        <Link
          href={`/my-corner/${post.slug}`}
          className="text-primary-600 hover:text-primary-800 font-medium"
        >
          Read more →
        </Link>
      </div>
    </article>
  );
}
```

- [ ] **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 (
    <html lang="en">
      <body className={`${inter.variable} ${playfair.variable} font-body`}>
        <TetEffects />
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}
```

- [ ] **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 (
    <>
      <Hero />
      <About />
      <Experience />
      <Contact />
    </>
  );
}
```

- [ ] **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 (
    <div className="min-h-screen bg-gray-50 pt-24 pb-12">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6 }}
          className="text-center mb-16"
        >
          <h1 className="text-4xl font-heading font-bold text-primary-700 mb-4">
            Projects
          </h1>
          <div className="w-24 h-1 bg-accent-500 mx-auto" />
        </motion.div>

        <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
          {projects.map((project) => (
            <ProjectCard key={project.id} project={project} />
          ))}
        </div>
      </div>
    </div>
  );
}
```

- [ ] **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 (
    <div className="min-h-screen bg-white pt-24 pb-12">
      <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6 }}
          className="text-center mb-16"
        >
          <h1 className="text-4xl font-heading font-bold text-primary-700 mb-4">
            My Corner
          </h1>
          <p className="text-lg text-gray-600">
            Personal stories, memories, and reflections
          </p>
        </motion.div>

        <div className="space-y-8">
          {blogPosts.map((post) => (
            <BlogCard key={post.slug} post={post} />
          ))}
        </div>
      </div>
    </div>
  );
}
```

- [ ] **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 (
    <div className="min-h-screen bg-white pt-24 pb-12">
      <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6 }}
        >
          <Link
            href="/my-corner"
            className="inline-block text-primary-600 hover:text-primary-800 mb-6"
          >
            ← Back to My Corner
          </Link>

          <article>
            <h1 className="text-4xl font-heading font-bold text-primary-700 mb-4">
              {post.title}
            </h1>
            <p className="text-gray-500 mb-8">{post.date}</p>

            {post.image && (
              <div className="relative h-64 w-full mb-8 rounded-2xl overflow-hidden">
                <img
                  src={post.image}
                  alt={post.title}
                  className="object-cover w-full h-full"
                />
              </div>
            )}

            <div className="prose prose-lg max-w-none text-gray-700">
              {/* Simple line-based rendering; for richer markdown, add 'react-markdown' */}
              {post.content.split("\n").map((line, i) => {
                if (line.startsWith("# ")) {
                  return (
                    <h1 key={i} className="text-3xl font-bold my-4">
                      {line.slice(2)}
                    </h1>
                  );
                }
                if (line.startsWith("## ")) {
                  return (
                    <h2 key={i} className="text-2xl font-bold my-4">
                      {line.slice(3)}
                    </h2>
                  );
                }
                if (line.startsWith("- ")) {
                  return (
                    <li key={i} className="ml-6 list-disc">
                      {line.slice(2)}
                    </li>
                  );
                }
                if (line.trim() === "") {
                  return <br key={i} />;
                }
                return (
                  <p key={i} className="mb-4">
                    {line}
                  </p>
                );
              })}
            </div>
          </article>
        </motion.div>
      </div>
    </div>
  );
}
```

- [ ] **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.


No messages


Now I understand the plan. The Hero section is Task 5. However, my task is currently BLOCKED by two other tasks:
1. 685f1607: "Initial Setup Next.js project" (in_progress by init agent)
2. 5b75def0: "Create data layer" (blocked by 685f1607)

So I need to wait for the initialization and data layer to complete before I can build the Hero section.

But let me check the repo to see if there's already some work done. The workspace is based on `/home/node/.openclaw/workspace/projects/clawteam-portfolio` according to the registry. Let me check if that repo has any existing files.

Let me check the current state of the repository to understand what's already been initialized:

cd /home/node/.openclaw/workspace/projects/clawteam-portfolio && git status && git log --oneline -5

cd /home/node/.openclaw/workspace/projects/clawteam-portfolio && ls -la


On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    NguyenNgocTriVi_CV.md
	deleted:    avatar.jpeg
	deleted:    implementation-plan.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.gitignore
	README.md
	eslint.config.mjs
	next.config.ts
	package.json
	postcss.config.mjs
	public/
	src/
	tsconfig.json

no changes added to commit (use "git add" and/or "git commit -a")
2fbeaac Add implementation plan
b43333b Initial: project assets (avatar, CV)


total 52
drwxr-xr-x 5 node node 4096 Mar 31 03:20 .
drwxr-xr-x 4 node node 4096 Mar 30 09:12 ..
drwxr-xr-x 9 node node 4096 Mar 31 03:20 .git
-rw-r--r-- 1 node node  480 Mar 31 03:20 .gitignore
-rw-r--r-- 1 node node 1450 Mar 31 03:20 README.md
-rw-r--r-- 1 node node  465 Mar 31 03:20 eslint.config.mjs
-rw-r--r-- 1 node node  247 Mar 31 03:20 next-env.d.ts
-rw-r--r-- 1 node node  133 Mar 31 03:20 next.config.ts
-rw-r--r-- 1 node node  540 Mar 31 03:20 package.json
-rw-r--r-- 1 node node   94 Mar 31 03:20 postcss.config.mjs
drwxr-xr-x 2 node node 4096 Mar 31 03:20 public
drwxr-xr-x 3 node node 4096 Mar 31 03:20 src
-rw-r--r-- 1 node node  670 Mar 31 03:20 tsconfig.json


The init agent has already set up the Next.js project structure! The files exist in the main repo. The init task is marked "in_progress" but the files are already there. Let me check if the data layer exists too.

cd /home/node/.openclaw/workspace/projects/clawteam-portfolio && find src -type f -name "*.ts" -o -name "*.tsx" | head -20

clawteam task list portfolio-tet-2026 --owner init


src/app/layout.tsx
src/app/page.tsx


Tasks - portfolio-tet-2026
┏━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┓
┃ ID       ┃ Subject      ┃ Status      ┃ Priority ┃ Owner ┃ Lock ┃ Blocked By ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━━━━━┩
│ 685f1607 │ Initialize   │ in_progress │ high     │ init  │ init │            │
│          │ Next.js      │             │          │       │      │            │
│          │ project      │             │          │       │      │            │
└──────────┴──────────────┴─────────────┴──────────┴───────┴──────┴────────────┘


The init agent's task (685f1607) is in progress but only the basic Next.js files exist. The implementation plan mentions that Task 2 (Create data layer) should have been completed with files like `src/data/profile.ts`, etc. Let me check if those exist.

Based on the task list, my hero task is blocked by both 685f1607 (Init) and 5b75def0 (Create data layer). The data layer task is owned by "data" agent and is blocked by 685f1607.

So I need to wait until the init and data tasks are completed. But I can verify the current state of the code and maybe check if I can start if the dependencies are actually met.

Let me check the src directory structure to see what's there.

cd /home/node/.openclaw/workspace/projects/clawteam-portfolio && tree -L 3 src/ 2>/dev/null || find src -type f -o -type d | sort

clawteam task list portfolio-tet-2026 --owner data


src
src/app
src/app/favicon.ico
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx


Tasks - portfolio-tet-2026
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┓
┃ ID       ┃ Subject          ┃ Status  ┃ Priority ┃ Owner ┃ Lock ┃ Blocked By ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━━━━━┩
│ 5b75def0 │ Create data      │ blocked │ high     │ data  │      │ 685f1607   │
│          │ layer            │         │          │       │      │            │
└──────────┴──────────────────┴─────────┴──────────┴───────┴──────┴────────────┘


So the current state is:
- Init (685f1607) is in_progress but only basic Next.js structure exists (layout, page, globals.css)
- Data layer (5b75def0) is blocked by init and hasn't started yet
- Hero (c5f11815) is blocked by both init and data

According to the coordination protocol, I should wait for dependencies. However, I can check if the init task is actually complete or if it's just marked in_progress but not finished. Let me check the init agent's progress.

I should also verify if the dependencies are truly blocking or if I can start working on the Hero section if I create the necessary data files myself. But the plan says the Hero section depends on the data layer (for profile data). Let me read the Hero component specification - it imports `profile` from `@/data/profile`. So I need the data layer to exist.

Given that the init task is "in_progress" but there's only basic Next.js files, it seems the init agent hasn't completed all steps yet (like installing framer-motion, configuring tailwind with custom colors, creating src/components directory, etc.).

I should wait for the dependencies to clear. In the meantime, I can:
1. Review the implementation plan in detail
2. Prepare my workspace
3. Check for any messages from the leader

Let me check the inbox again for any messages and also verify the hero worktree status.

Let me check the state of my hero worktree and look for any new messages:

cd /home/node/.clawteam/workspaces/portfolio-tet-2026/hero && git status && git log --oneline -3

clawteam inbox receive portfolio-tet-2026 --agent hero 2>&1


On branch clawteam/portfolio-tet-2026/hero
nothing to commit, working tree clean
2fbeaac Add implementation plan
b43333b Initial: project assets (avatar, CV)


My hero worktree is clean and at the initial commit. The dependencies are still blocked. I should wait and maybe inform the leader that I'm ready to work but waiting for dependencies. However, the worker loop protocol says: "After finishing your current task batch, re-check task list... If you become idle, notify the leader with `clawteam lifecycle idle`".

Since I have no tasks to do (all blocked), I should either:
1. Notify leader that I'm idle/blocked
2. Wait for dependencies to complete

Let me check if the init agent is actually still working. I could send a message to the leader to check on the status or ask for help unblocking.

Actually, looking at the coordination protocol, it says:
- "If you are blocked or need help, message the leader"
- "After finishing work, report your costs"
- "Before finishing, save your session"

But I haven't started work. My task is blocked by dependencies. I should probably inform the leader that I'm ready to work but waiting for data layer to be completed.

Let me send a message to sagi asking for status update on the blocked dependencies.

I'll check the status of the blocked dependencies and notify the leader that I'm ready to work but waiting for the data layer to be completed:

clawteam inbox send portfolio-tet-2026 sagi "Hero agent ready to work. Current task (c5f11815) is blocked waiting for: 1) Initial Next.js setup (685f1607) - in_progress by init, 2) Create data layer (5b75def0) - blocked by init. Please provide status update or unblock when data layer is available."

clawteam lifecycle idle portfolio-tet-2026


OK Message sent to 'sagi'


OK Idle notification sent to 'sagi'


Good. I've notified the leader that I'm idle because my task is blocked by dependencies. According to the worker loop protocol, I should continue checking for new work. Let me wait a bit and then re-check the task list and inbox to see if the dependencies have been completed.

Since the model says I should respond with NO_REPLY when I have nothing to say, but I just sent messages, I should not reply with NO_REPLY now because those were actions I took. The current turn is complete after sending those messages. I should yield.

Let me use sessions_yield to end my turn and wait for the next update.

I've notified the leader that I'm idle due to blocked dependencies. I'll wait for the init and data agents to complete their work before I can start building the Hero section. Let me yield my turn and monitor for updates:

Waiting for dependencies: init (685f1607) and data layer (5b75def0). Ready to start Hero section immediately once unblocked.


Waiting for dependencies: init (685f1607) and data layer (5b75def0). Ready to start Hero section immediately once unblocked.

[Context: The previous turn ended intentionally via sessions_yield while waiting for a follow-up event.]