OpenClaw + ClawTeam + Superpowers Git Initialization

This commit is contained in:
2026-04-01 11:31:29 +07:00
parent b49ba1a719
commit 1eef79cae7
259 changed files with 0 additions and 50195 deletions
Submodule .openclaw/workspace/projects/clawteam-portfolio deleted from 9bf7d92e7e
@@ -1,169 +0,0 @@
# Lô Tô Online — Product Requirements Document (PRD)
---
## 1. Vision
Mang trải nghiệm **Lô Tô truyền thống miền Nam** lên nền tảng Zalo Mini App với phong cách hoài niệm tuổi thơ: giấy cũ ố vàng, rổ tre, túi vải đen đựng số, hạt gạo đặt lên vé. Người chơi tự tay bấm đặt hạt gạo — đúng sai tự chịu, giữ nguyên cảm giác hồi hộp của Lô Tô đời thực.
**MVP đầu tiên** sẽ chạy trên **web (mobile-first)** để thử nghiệm, sau đó chuyển sang **Zalo Mini App** khi ổn định.
---
## 2. Target Users
| Persona | Mô tả |
|---|---|
| Người Việt 2045 tuổi | Lớn lên với Lô Tô, muốn hoài niệm |
| Nhóm bạn / gia đình | Chơi giải trí online cùng nhau |
| Casual gamer | Thích game đơn giản, may rủi, vui nhộn |
---
## 3. Core Game Rules (Lô Tô truyền thống)
### 3.1 Vé Lô Tô
- Bảng **3 hàng × 9 cột** = 27 ô
- Mỗi hàng có đúng **5 ô số** + 4 ô trống
- Số từ **190**, mỗi cột chứa số thuộc hàng chục tương ứng (cột 1: 19, cột 2: 1019, …, cột 9: 8090)
- Mỗi vé có **15 số** duy nhất, không trùng
### 3.2 Lượt chơi
1. Hệ thống **lắc túi vải****bốc 1 viên số** ngẫu nhiên (190, không lặp)
2. Hiển thị animation bốc số + **đọc số bằng giọng miền Nam**
3. Người chơi có **15 giây** (cài đặt khi tạo phòng) để **tự bấm đặt hạt gạo** vào ô tương ứng
4. Hết thời gian highlight → người hô đọc số tiếp, **không chờ** ai
5. Người chơi vẫn có thể **đặt muộn** (theo trí nhớ) bất cứ lúc nào
### 3.3 Điều kiện "Kinh" (Thắng)
- Hoàn thành **1 hàng ngang** (5/5 ô số trong hàng đều có hạt gạo)
-**"Kinh!"** → hệ thống verify
### 3.4 Verify & Phạt
| Trường hợp | Kết quả |
|---|---|
| Tất cả hạt gạo đúng ô đã bốc | ✅ **Thắng** — nhận tiền thưởng từ tất cả người thua. Nếu có nhiều người cùng Kinh đúng ở một lượt, tiền thưởng được **chia đều** |
| Có ≥1 hạt gạo đặt nhầm ô (ô chưa được bốc) | ❌ **Phạt** — đền bằng **mức cược của phòng** cho **mỗi** người chơi còn lại |
### 3.5 Kết thúc ván
- Khi có người **kinh hợp lệ** → ván kết thúc (hoặc kết thúc lượt bốc hiện tại để xét những người kinh cùng lúc)
- Hoặc khi bốc hết 90 số mà chưa ai kinh → hoà (trả lại cược)
---
## 4. Features
### 4.1 Lobby & Phòng chơi
| Feature | Chi tiết |
|---|---|
| Lobby công khai | Danh sách phòng đang chờ, lọc theo mức cược |
| Tạo phòng | Chủ phòng cài đặt: mức cược, thời gian highlight (15s), số người (28) |
| Vào phòng | Click vào phòng trong lobby |
| Chờ & sẵn sàng | Tất cả sẵn sàng → bắt đầu |
| Rời phòng | Rời giữa chừng = bỏ cuộc, mất cược |
### 4.2 Gameplay
| Feature | Chi tiết |
|---|---|
| Lắc túi vải | Animation túi vải đen mềm mại + vật lý khi lắc |
| Bốc số | Animation tay rút viên số từ túi ra |
| Đọc số | Giọng đọc miền Nam truyền thống |
| Đặt hạt gạo | Click vào ô trên vé → hạt gạo rơi xuống (animation) |
| Đặt muộn | Vẫn bấm được sau khi hết highlight |
| Hô kinh | Nút hô kinh khi nghĩ mình hoàn thành hàng |
| Verify | Hệ thống kiểm tra → thắng hoặc phạt |
### 4.3 Economy (Hệ thống tiền tệ)
| Feature | Chi tiết |
|---|---|
| Đơn vị tiền | **"Đồng"** (xu ảo) |
| Nhận đồng miễn phí | Sự kiện hàng ngày, nhiệm vụ, hoạt động |
| Nạp tiền | Mua đồng bằng tiền thật (IAP / Zalo Pay) |
| Cược | Đặt cược khi vào phòng |
| Thắng | Nhận cược từ tất cả người chơi thua |
| Phạt kinh sai | Trả mức cược × số người chơi còn lại |
### 4.4 Profile & Xếp hạng
| Feature | Chi tiết |
|---|---|
| Đăng nhập Zalo | Dùng Zalo account, lấy avatar + tên |
| Profile | Tổng đồng, số ván thắng/thua, tỷ lệ thắng |
| Lịch sử chơi | Danh sách ván đã chơi, kết quả, thời gian |
| Bảng xếp hạng | Theo tổng đồng kiếm được |
### 4.5 Social (Trong phòng)
| Feature | Chi tiết |
|---|---|
| Chat | Nhắn tin trong phòng chơi |
| Reactions / Emoji | Gửi emoji nổi lên màn hình |
### 4.6 Audio
| Feature | Chi tiết |
|---|---|
| Giọng đọc số | Giọng miền Nam, đọc từng số theo kiểu lô tô |
| Nhạc nền | Vui tươi, gợi hoài niệm tuổi thơ |
| SFX — đặt hạt gạo | Tiếng gạo rơi nhẹ |
| SFX — đợi | Tiếng hò reo nhỏ, xì xào khi đợi |
| SFX — kinh | La lên, pháo hoa, reo hò |
| SFX — kinh sai | Tiếng thở dài, ồ tiếc nuối |
---
## 5. Art Direction (Phong cách hoài niệm)
| Yếu tố | Chi tiết |
|---|---|
| **Nền** | Giấy cũ ố vàng, texture gỗ mộc |
| **Vé lô tô** | Giấy carton cũ, viền tròn, chữ in mờ kiểu cũ |
| **Túi số** | Vải đen mềm, animation vật lý khi lắc |
| **Viên số** | Bi gỗ tròn, khắc số đỏ |
| **Hạt gạo** | Hạt gạo trắng thật, có bóng đổ nhẹ |
| **Rổ tre** | Đựng các viên số đã bốc |
| **Font** | Handwritten / retro Việt Nam |
| **Màu chủ đạo** | Nâu gỗ, vàng ố, đỏ đất, xanh lá cũ |
---
## 6. Platform & Technical Constraints
| Hạng mục | Chi tiết |
|---|---|
| **MVP platform** | Web (mobile-first), responsive cho điện thoại |
| **Production platform** | Zalo Mini App |
| **Multiplayer** | Real-time (WebSocket) |
| **Min players** | 2 |
| **Max players** | 8 |
| **Target device** | Mobile only |
---
## 7. Out of Scope (v1)
- Tablet / Desktop optimized layout
- Tournament mode
- Friend list / invite via Zalo
- Spectator mode
- AI bot players
- Multiple vé per player
---
## 8. Success Metrics
| Metric | Target |
|---|---|
| Prototype hoàn chỉnh | Full gameplay + multiplayer + animation + audio |
| Latency bốc số → hiện | < 200ms |
| Animation FPS | ≥ 30fps trên mid-range phone |
| Ván chơi trung bình | 38 phút |
@@ -1,41 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
@@ -1,5 +0,0 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->
@@ -1 +0,0 @@
@AGENTS.md
@@ -1,69 +0,0 @@
# Nguyen Ngoc Tri Vi (Thomas)
**Portfolio:** [nguyenngoctrivi.vercel.app](https://nguyenngoctrivi.vercel.app) | **SĐT:** 090 321 5095 | **Email:** nntrivi2001@gmail.com | **LinkedIn:** [linkedin.com/in/nguyen-ngoc-tri-vi](https://linkedin.com/in/nguyen-ngoc-tri-vi)
---
## Kinh nghiệm làm việc
### SmartGift Solution
**Kỹ sư AI** | Tháng 03/2026 - Hiện tại
* Nghiên cứu và triển khai **AI hỗ trợ** nhằm cải thiện hiệu suất của đội ngũ phát triển.
* Khám phá và áp dụng các công nghệ phù hợp cho các bài toán kinh doanh, xây dựng các bản **thử nghiệm (POC)** và tài liệu hóa các đề xuất thực tế.
### Learning Chain Vietnam
**Nghiên cứu viên AI Ứng dụng & Cộng tác viên Vận hành** | Tháng 07/2025 - Tháng 03/2026
* Nghiên cứu và triển khai **tự động hóa giao dịch** và vận hành có hỗ trợ AI.
* Khám phá và thực thi công nghệ phù hợp, xây dựng POC và tài liệu hóa các khuyến nghị thực tế.
* Thiết kế và giảng dạy chương trình đào tạo **AI ứng dụng**, các buổi hội thảo và tài sản học tập (mẫu n8n).
* Hỗ trợ Giám đốc vận hành (COO) trong việc **tối ưu hóa quy trình làm việc**, báo cáo và tiêu chuẩn hóa quy trình liên nhóm.
### Vườn đậu - Thương hiệu Sữa đậu nành Thảo mộc
**Người sáng lập (Founder)** | Tháng 10/2024 - Tháng 04/2025
* Quản lý toàn bộ quy trình sản xuất và kinh doanh, bao gồm nguồn cung, sản xuất, đóng gói, phân phối và **kiểm soát hàng tồn kho**.
* Lập kế hoạch thu mua nguyên liệu, lịch trình sản xuất và lưu kho để **tối ưu hóa chi phí** và duy trì chất lượng sản phẩm ổn định.
* Phát triển chiến lược giá và triển khai các kênh bán hàng trực tiếp. Đạt được phản hồi tích cực từ khách hàng với tỷ lệ mua lại trung bình **2.51 lần mỗi khách hàng**.
### VSTECH Company Limited
**Kỹ sư Thị giác Máy tính (Computer Vision Engineer)** | Tháng 08/2023 - Tháng 02/2025
* **Kiểm tra chất lượng vòi phun (Nozzle Quality Inspection):** Xây dựng cho khách hàng Nhật Bản để triển khai trên dây chuyền sản xuất nhà máy.
* **Tích hợp hệ thống thị giác 3D:** Đo chiều cao tấm nền LED Samsung với độ chính xác đạt +-7%.
* **Giải pháp OCR:** Đọc mã gói hàng và nhãn văn bản với độ chính xác cao trong môi trường công nghiệp.
---
## Học vấn
### Đại học Công Thương TP.HCM
**Kỹ sư Phân tích Dữ liệu & AI** | Tháng 10/2020 - Tháng 10/2024
* Tốt nghiệp **Thủ khoa** ngành Công nghệ Thông tin.
* **GPA:** 3.74/4.0.
* **Giải thưởng & Học bổng:**
* Giải Nhì - Cuộc thi Sản phẩm và Chủ đề IT Thiết yếu (2024).
* Giải Khuyến khích - TOFAS (2023).
* Giải Mã nguồn sạch (Clean Code) & Giải Khuyến khích - Tìm kiếm Tài năng & Sản phẩm IT Sáng tạo (2022).
* Nhiều học bổng Sinh viên Xuất sắc & Giỏi (2020-2024).
---
## Kỹ năng
| Danh mục | Chi tiết |
| :--- | :--- |
| **Lập trình & Scripting** | Python, C++, HTML/CSS, JavaScript, PineScript, n8n, Telegram bots |
| **Framework AI/ML** | TensorFlow, PyTorch, Scikit-learn, OpenCV |
| **Dữ liệu & Đám mây** | SQL Server, Supabase, Firebase, MongoDB |
| **Quản lý & Vận hành** | Tối ưu hóa quy trình, phối hợp liên nhóm, tài liệu hóa |
| **Ngôn ngữ** | Tiếng Anh (Đọc chuyên ngành, giao tiếp trung cấp), Tiếng Việt (Bản ngữ) |
---
## Portfolio & Chứng chỉ
* Các dự án khác và bản demo GIF có sẵn trên trang **Portfolio**.
* Các chứng chỉ chuyên môn được liệt kê chi tiết trên **LinkedIn**.
@@ -1,36 +0,0 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -1,18 +0,0 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
@@ -1,19 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
turbopack: {
root: process.cwd(),
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
// Allow local file system images via next/image
unoptimized: true,
},
};
export default nextConfig;
File diff suppressed because it is too large Load Diff
@@ -1,28 +0,0 @@
{
"name": "my-portfolio",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"framer-motion": "^12.38.0",
"lucide-react": "^1.7.0",
"next": "16.2.1",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.2",
"@types/node": "20.19.37",
"@types/react": "19.2.14",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.2.1",
"tailwindcss": "^4.2.2",
"typescript": "5.9.3"
}
}
@@ -1,22 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<defs>
<linearGradient id="avatarGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#DC2626;stop-opacity:1" />
<stop offset="100%" style="stop-color:#F59E0B;stop-opacity:1" />
</linearGradient>
</defs>
<circle cx="100" cy="100" r="100" fill="url(#avatarGradient)" />
<circle cx="100" cy="100" r="92" fill="white" fill-opacity="0.1" />
<text
x="100"
y="115"
font-family="'Segoe UI', Arial, sans-serif"
font-size="80"
font-weight="bold"
fill="white"
text-anchor="middle"
dominant-baseline="middle"
style="text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">
TV
</text>
</svg>

Before

Width:  |  Height:  |  Size: 756 B

@@ -1 +0,0 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 391 B

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 128 B

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

Before

Width:  |  Height:  |  Size: 385 B

@@ -1,174 +0,0 @@
'use client';
import { GraduationCapIcon, AwardIcon, LanguagesIcon, MapPinIcon, MailIcon, PhoneIcon } from '@/components/icons/TetIcons';
import ScrollReveal from '@/components/animations/ScrollReveal';
export default function AboutPage() {
return (
<main className="min-h-screen py-20 px-4 sm:px-6 lg:px-8 bg-[var(--tet-cream)]">
<div className="max-w-6xl mx-auto">
{/* Page Header */}
<ScrollReveal variant="fadeInUp">
<div className="text-center mb-16">
<h1 className="text-5xl font-bold text-[var(--tet-red)] mb-4">About Me</h1>
<div className="h-1 w-32 bg-[var(--tet-gold)] mx-auto rounded-full" />
</div>
</ScrollReveal>
<div className="grid md:grid-cols-2 gap-12 items-start">
{/* Avatar Section */}
<ScrollReveal variant="fadeInUp" delay={0.1}>
<div className="flex flex-col items-center">
<div className="relative w-80 h-80 rounded-full overflow-hidden border-8 border-[var(--tet-gold)] shadow-2xl mb-6 bg-gradient-to-br from-[var(--tet-red)] to-[var(--tet-gold)] flex items-center justify-center">
<span className="text-6xl font-bold text-white">TV</span>
</div>
{/* Quick contact */}
<div className="space-y-3 w-full max-w-md">
<div className="flex items-center space-x-3 text-gray-700">
<MapPinIcon className="w-5 h-5 text-[var(--tet-red)]" />
<span>Ho Chi Minh City, Vietnam</span>
</div>
<div className="flex items-center space-x-3 text-gray-700">
<MailIcon className="w-5 h-5 text-[var(--tet-red)]" />
<a href="mailto:nntrivi2001@gmail.com" className="hover:text-[var(--tet-red)] transition">
nntrivi2001@gmail.com
</a>
</div>
<div className="flex items-center space-x-3 text-gray-700">
<PhoneIcon className="w-5 h-5 text-[var(--tet-red)]" />
<span>090 321 5095</span>
</div>
</div>
</ScrollReveal>
{/* Bio Content */}
<ScrollReveal variant="fadeInUp" delay={0.2}>
<div className="space-y-8">
{/* Introduction */}
<section className="bg-white p-8 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg">
<h2 className="text-2xl font-bold text-[var(--tet-red)] mb-4">Who I Am</h2>
<p className="text-gray-700 leading-relaxed mb-4">
I&apos;m <strong>Nguyễn Ngọc Trí </strong> (Thomas), an AI Engineer with a passion for building intelligent systems
that solve real-world problems. I graduated as <span className="text-[var(--tet-gold)] font-semibold">Valedictorian</span> with a GPA of 3.74/4.0
from Ho Chi Minh City University of Industry and Trade, majoring in Data Analytics & AI.
</p>
<p className="text-gray-700 leading-relaxed">
My journey spans from computer vision in manufacturing to applied AI research and workflow automation.
I believe in the power of AI to transform businesses and improve lives, and I&apos;m committed to pushing
the boundaries of what&apos;s possible with technology.
</p>
</section>
{/* Education */}
<section className="bg-white p-8 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg">
<div className="flex items-center space-x-3 mb-4">
<GraduationCapIcon className="w-8 h-8 text-[var(--tet-red)]" />
<h2 className="text-2xl font-bold text-[var(--tet-red)]">Education</h2>
</div>
<div className="border-l-4 border-[var(--tet-gold)] pl-6 py-2">
<h3 className="text-xl font-semibold text-gray-800">
Engineer in Data Analytics & AI
</h3>
<p className="text-[var(--tet-red)] font-medium">Ho Chi Minh City University of Industry and Trade</p>
<p className="text-gray-600">Oct 2020 Oct 2024</p>
<div className="mt-3 flex flex-wrap gap-2">
<span className="px-3 py-1 bg-[var(--tet-red)]/10 text-[var(--tet-red)] rounded-full text-sm font-medium">
Valedictorian 🎓
</span>
<span className="px-3 py-1 bg-[var(--tet-gold)]/20 text-amber-700 rounded-full text-sm font-medium">
GPA: 3.74 / 4.0
</span>
</div>
</div>
{/* Awards */}
<div className="mt-6">
<h4 className="font-semibold text-gray-800 mb-3">Awards & Achievements</h4>
<ul className="space-y-2">
{[
"🥈 2nd Place Essential IT Products Competition (2024)",
"🥉 Consolation Prize TOFAS (2023)",
"🏆 Clean Code Award Finding Talents & Innovative IT (2022)",
"📜 Multiple scholarships (20202024)",
].map((award, idx) => (
<li key={idx} className="flex items-start space-x-2 text-gray-700">
<span className="text-[var(--tet-gold)]">{award}</span>
</li>
))}
</ul>
</div>
</section>
{/* Skills & Languages */}
<div className="grid md:grid-cols-2 gap-6">
<section className="bg-white p-6 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg">
<h3 className="text-xl font-bold text-[var(--tet-red)] mb-4">Skills</h3>
<div className="space-y-3">
<div>
<p className="font-semibold text-gray-800 mb-2">Programming & Tools</p>
<div className="flex flex-wrap gap-2">
{['Python', 'C++', 'HTML/CSS', 'JavaScript', 'PineScript', 'n8n', 'Telegram Bots'].map(skill => (
<span key={skill} className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">
{skill}
</span>
))}
</div>
</div>
<div>
<p className="font-semibold text-gray-800 mb-2">AI / ML</p>
<div className="flex flex-wrap gap-2">
{['TensorFlow', 'PyTorch', 'Scikit-learn', 'OpenCV'].map(skill => (
<span key={skill} className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">
{skill}
</span>
))}
</div>
</div>
<div>
<p className="font-semibold text-gray-800 mb-2">Data & Cloud</p>
<div className="flex flex-wrap gap-2">
{['SQL Server', 'Supabase', 'Firebase', 'MongoDB'].map(skill => (
<span key={skill} className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">
{skill}
</span>
))}
</div>
</div>
</div>
</section>
<section className="bg-white p-6 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg">
<div className="flex items-center space-x-3 mb-4">
<LanguagesIcon className="w-6 h-6 text-[var(--tet-red)]" />
<h3 className="text-xl font-bold text-[var(--tet-red)]">Languages</h3>
</div>
<div className="space-y-4">
<div>
<div className="flex justify-between mb-1">
<span className="font-medium text-gray-800">Vietnamese</span>
<span className="text-sm text-gray-600">Native</span>
</div>
<div className="h-2 bg-gray-200 rounded-full">
<div className="h-2 bg-[var(--tet-red)] rounded-full" style={{ width: '100%' }} />
</div>
</div>
<div>
<div className="flex justify-between mb-1">
<span className="font-medium text-gray-800">English</span>
<span className="text-sm text-gray-600">Professional</span>
</div>
<div className="h-2 bg-gray-200 rounded-full">
<div className="h-2 bg-[var(--tet-gold)] rounded-full" style={{ width: '85%' }} />
</div>
</div>
</div>
</section>
</div>
</ScrollReveal>
</div>
</div>
</main>
);
}
@@ -1,248 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { MailIcon, PhoneIcon, MapPinIcon, SendIcon, LinkedInIcon, GithubIcon, ArrowRightIcon } from '@/components/icons/TetIcons';
import { useState } from 'react';
import ScrollReveal from '@/components/animations/ScrollReveal';
export default function ContactPage() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: '',
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate form submission (in production, connect to backend)
await new Promise(resolve => setTimeout(resolve, 1500));
setSubmitStatus('success');
setIsSubmitting(false);
setFormData({ name: '', email: '', subject: '', message: '' });
setTimeout(() => setSubmitStatus('idle'), 5000);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
return (
<main className="min-h-screen py-20 px-4 sm:px-6 lg:px-8 bg-[var(--tet-cream)]">
<div className="max-w-6xl mx-auto">
{/* Page Header */}
<ScrollReveal variant="fadeInUp">
<div className="text-center mb-16">
<h1 className="text-5xl font-bold text-[var(--tet-red)] mb-4">Get in Touch</h1>
<div className="h-1 w-32 bg-[var(--tet-gold)] mx-auto rounded-full mb-4" />
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
Have a project in mind or want to collaborate? I&apos;d love to hear from you.
Don&apos;t hesitate to reach out!
</p>
</div>
</ScrollReveal>
<div className="grid lg:grid-cols-3 gap-12">
{/* Contact Information */}
<ScrollReveal variant="fadeInUp" delay={0.1} className="lg:col-span-1 space-y-6">
<div className="bg-white p-8 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg">
<h2 className="text-2xl font-bold text-[var(--tet-red)] mb-6">Contact Info</h2>
<div className="space-y-6">
<div className="flex items-start space-x-4">
<div className="p-3 bg-[var(--tet-red)]/10 rounded-xl">
<MailIcon className="w-6 h-6 text-[var(--tet-red)]" />
</div>
<div>
<p className="font-semibold text-gray-800">Email</p>
<a
href="mailto:nntrivi2001@gmail.com"
className="text-gray-600 hover:text-[var(--tet-red)] transition-colors"
>
nntrivi2001@gmail.com
</a>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="p-3 bg-[var(--tet-red)]/10 rounded-xl">
<PhoneIcon className="w-6 h-6 text-[var(--tet-red)]" />
</div>
<div>
<p className="font-semibold text-gray-800">Phone</p>
<a
href="tel:+84903215095"
className="text-gray-600 hover:text-[var(--tet-red)] transition-colors"
>
090 321 5095
</a>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="p-3 bg-[var(--tet-red)]/10 rounded-xl">
<MapPinIcon className="w-6 h-6 text-[var(--tet-red)]" />
</div>
<div>
<p className="font-semibold text-gray-800">Location</p>
<p className="text-gray-600">Ho Chi Minh City, Vietnam</p>
</div>
</div>
</div>
{/* Social Links */}
<div className="mt-8 pt-8 border-t border-gray-200">
<h3 className="font-semibold text-gray-800 mb-4">Connect with me</h3>
<div className="flex space-x-4">
<a
href="https://linkedin.com/in/nguyen-ngoc-tri-vi"
target="_blank"
rel="noopener noreferrer"
className="p-3 bg-[var(--tet-red)]/10 rounded-xl hover:bg-[var(--tet-red)]/20 transition-colors group"
aria-label="LinkedIn"
>
<LinkedInIcon className="w-6 h-6 text-[var(--tet-red)] group-hover:scale-110 transition-transform" />
</a>
<a
href="https://nguyenngoctrivi.vercel.app/"
target="_blank"
rel="noopener noreferrer"
className="p-3 bg-[var(--tet-gold)]/20 rounded-xl hover:bg-[var(--tet-gold)]/30 transition-colors group"
aria-label="Portfolio"
>
<ArrowRightIcon className="w-6 h-6 text-amber-600 group-hover:scale-110 transition-transform" />
</a>
</div>
</div>
</div>
{/* Decorative element */}
<div className="text-center text-6xl hidden lg:block">🧧</div>
</ScrollReveal>
{/* Contact Form */}
<ScrollReveal variant="fadeInUp" delay={0.2} className="lg:col-span-2">
<div className="bg-white p-8 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg">
<h2 className="text-2xl font-bold text-[var(--tet-red)] mb-6">Send a Message</h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid md:grid-cols-2 gap-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
Your Name *
</label>
<input
type="text"
id="name"
name="name"
required
value={formData.name}
onChange={handleChange}
className="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-[var(--tet-red)] focus:outline-none transition-colors"
placeholder="Nguyễn Văn A"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
Email Address *
</label>
<input
type="email"
id="email"
name="email"
required
value={formData.email}
onChange={handleChange}
className="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-[var(--tet-red)] focus:outline-none transition-colors"
placeholder="example@email.com"
/>
</div>
</div>
<div>
<label htmlFor="subject" className="block text-sm font-medium text-gray-700 mb-2">
Subject
</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
className="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-[var(--tet-red)] focus:outline-none transition-colors"
placeholder="What's this about?"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-2">
Message *
</label>
<textarea
id="message"
name="message"
required
rows={6}
value={formData.message}
onChange={handleChange}
className="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-[var(--tet-red)] focus:outline-none transition-colors resize-none"
placeholder="Tell me about your project or opportunity..."
/>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">
* Required fields
</p>
<button
type="submit"
disabled={isSubmitting}
className="inline-flex items-center gap-2 px-8 py-4 bg-[var(--tet-red)] text-white rounded-full font-semibold hover:bg-[var(--tet-red-dark)] transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg hover:shadow-xl"
>
{isSubmitting ? (
'Sending...'
) : (
<>
Send Message
<SendIcon className="w-5 h-5" />
</>
)}
</button>
</div>
{/* Success/Error messages */}
{submitStatus === 'success' && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="p-4 bg-green-50 border-2 border-green-200 rounded-xl text-green-800"
>
Your message has been sent successfully! I&apos;ll get back to you soon.
</motion.div>
)}
{submitStatus === 'error' && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="p-4 bg-red-50 border-2 border-red-200 rounded-xl text-red-800"
>
Something went wrong. Please try again or email me directly.
</motion.div>
)}
</form>
</div>
</ScrollReveal>
</div>
</div>
</main>
);
}
@@ -1,137 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { BriefcaseIcon, CalendarIcon, MapPinIcon } from '@/components/icons/TetIcons';
import ScrollReveal from '@/components/animations/ScrollReveal';
const experiences = [
{
company: 'SmartGift Solution',
role: 'AI Engineer',
period: 'Mar 2026 Present',
location: 'Ho Chi Minh City, Vietnam',
description: [
'Researched and implemented AI-assisted solutions to improve developer team performance',
'Explored technologies for business use cases, built POCs, and documented recommendations',
],
color: 'tet-red',
},
{
company: 'Learning Chain Vietnam',
role: 'Applied AI Researcher & Operations Associate',
period: 'Jul 2025 Mar 2026',
location: 'Ho Chi Minh City, Vietnam',
description: [
'Built AI-assisted trading and operations automation systems',
'Developed POCs and recommended technologies for business use',
'Designed applied AI curricula, workshops, and n8n learning assets',
'Supported COO in workflow optimization and process standardization',
],
color: 'tet-gold',
},
{
company: 'Vườn Đậu Herbal Soy Milk Brand',
role: 'Founder',
period: 'Oct 2024 Apr 2025',
location: 'Ho Chi Minh City, Vietnam',
description: [
'Managed full production and business operations',
'Optimized sourcing, production, and inventory systems',
'Built pricing strategy and launched direct sales channels',
'Achieved strong customer retention (2.51 repeat purchases/customer)',
],
color: 'tet-red',
},
{
company: 'VSTECH Company Limited',
role: 'Computer Vision Engineer',
period: 'Aug 2023 Feb 2025',
location: 'Ho Chi Minh City, Vietnam',
description: [
'Developed nozzle quality inspection system for Japanese factory',
'Integrated 3D vision system for Samsung LED measurement (±7% accuracy)',
'Built OCR system for package code recognition in industrial environments',
],
color: 'tet-gold',
},
];
export default function ExperiencePage() {
return (
<main className="min-h-screen py-20 px-4 sm:px-6 lg:px-8 bg-[var(--tet-cream)]">
<div className="max-w-5xl mx-auto">
{/* Page Header */}
<ScrollReveal variant="fadeInUp">
<div className="text-center mb-16">
<h1 className="text-5xl font-bold text-[var(--tet-red)] mb-4">Experience</h1>
<div className="h-1 w-32 bg-[var(--tet-gold)] mx-auto rounded-full" />
</div>
</ScrollReveal>
{/* Timeline */}
<div className="relative">
{/* Central line */}
<div className="absolute left-8 md:left-1/2 top-0 bottom-0 w-0.5 bg-gradient-to-b from-[var(--tet-red)] to-[var(--tet-gold)] transform md:-translate-x-1/2" />
{experiences.map((exp, index) => {
const isEven = index % 2 === 0;
const Icon = BriefcaseIcon;
return (
<ScrollReveal key={index} variant={isEven ? "slideInLeft" : "slideInRight"} delay={index * 0.15}>
<div className={`relative flex items-center mb-16 ${isEven ? 'md:flex-row' : 'md:flex-row-reverse'}`}>
{/* Content Card */}
<div className={`ml-20 md:ml-0 md:w-5/12 ${isEven ? 'md:pr-12 md:text-right' : 'md:pl-12'} z-10`}>
<div className="bg-white p-6 rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg hover:shadow-xl transition-shadow duration-300 card-hover">
<div className="flex items-center gap-2 mb-2 justify-center md:justify-start">
<CalendarIcon className="w-4 h-4 text-[var(--tet-red)]" />
<span className="text-sm font-medium text-[var(--tet-red)]">{exp.period}</span>
</div>
<h3 className="text-2xl font-bold text-gray-800 mb-1">{exp.role}</h3>
<div className="flex items-center gap-2 mb-3 justify-center md:justify-start">
<span className="text-lg font-semibold text-[var(--tet-red)]">{exp.company}</span>
</div>
<div className="flex items-center gap-2 mb-4 text-gray-600 text-sm justify-center md:justify-start">
<MapPinIcon className="w-4 h-4" />
<span>{exp.location}</span>
</div>
<ul className="space-y-2 mb-4">
{exp.description.map((item, idx) => (
<li key={idx} className={`text-gray-700 ${isEven ? 'md:text-right' : 'md:text-left'}`}>
{item}
</li>
))}
</ul>
<div className="flex justify-center md:justify-start">
<span className={`px-3 py-1 rounded-full text-xs font-medium bg-${exp.color === 'tet-red' ? '[var(--tet-red)]/10' : '[var(--tet-gold)]/20'} text-${exp.color === 'tet-red' ? '[var(--tet-red)]' : 'amber-700'}`}>
{exp.color === 'tet-red' ? '🔴' : '🟡'} {exp.period.split(' ')[0]}
</span>
</div>
</div>
</div>
{/* Timeline dot */}
<div className="absolute left-8 md:left-1/2 transform -translate-x-1/2">
<motion.div
whileHover={{ scale: 1.3 }}
className="w-8 h-8 rounded-full bg-[var(--tet-gold)] border-4 border-[var(--tet-cream)] shadow-lg flex items-center justify-center z-20"
>
<Icon className="w-4 h-4 text-[var(--tet-dark)]" />
</motion.div>
</div>
{/* Empty spacer for opposite side */}
<div className="hidden md:block md:w-5/12" />
</div>
</ScrollReveal>
);
})}
</div>
</div>
</main>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

@@ -1,66 +0,0 @@
:root {
/* Tet Theme Colors */
--tet-red: #C41E3A;
--tet-red-dark: #8B0000;
--tet-gold: #FFD700;
--tet-gold-light: #FFC107;
--tet-cream: #FFF8E7;
--tet-dark: #1A1A1A;
--tet-red-gradient: linear-gradient(135deg, #C41E3A 0%, #8B0000 100%);
--tet-gold-gradient: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
--background: var(--tet-cream);
--foreground: var(--tet-dark);
}
/* Custom utility classes */
.bg-tet-red { background-color: var(--tet-red); }
.bg-tet-gold { background-color: var(--tet-gold); }
.text-tet-red { color: var(--tet-red); }
.text-tet-gold { color: var(--tet-gold); }
.border-tet-red { border-color: var(--tet-red); }
body {
background: var(--background);
color: var(--foreground);
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow-x: hidden;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Custom scrollbar with Tet colors */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: var(--tet-cream);
}
::-webkit-scrollbar-thumb {
background: var(--tet-red);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--tet-red-dark);
}
/* Golden glow effect */
.text-glow {
text-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
}
/* Red card hover effect */
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(196, 30, 58, 0.2);
}
@@ -1,101 +0,0 @@
'use client';
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Navigation from "@/components/layout/Navigation";
import Footer from "@/components/layout/Footer";
import Script from "next/script";
import { AnimatePresence, motion } from 'framer-motion';
import { usePathname } from 'next/navigation';
import FallingPetals from '@/components/animations/FallingPetals';
import { useEffect, useState } from 'react';
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Trí Vĩ - AI Engineer Portfolio",
description: "Portfolio of Nguyễn Ngọc Trí Vĩ - AI Engineer specializing in Computer Vision, Machine Learning, and Applied AI solutions",
keywords: ["AI Engineer", "Machine Learning", "Computer Vision", "Portfolio", "Vietnam"],
authors: [{ name: "Nguyễn Ngọc Trí Vĩ" }],
openGraph: {
title: "Trí Vĩ - AI Engineer",
description: "Portfolio of an AI Engineer building intelligent systems",
type: "website",
},
};
// Page transition variants
const pageVariants = {
hidden: { opacity: 0, x: 20 },
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.4,
ease: [0.25, 0.1, 0.25, 1]
}
},
exit: {
opacity: 0,
x: -20,
transition: {
duration: 0.3,
ease: [0.25, 0.1, 0.25, 1]
}
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const pathname = usePathname();
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col">
{/* Tailwind CSS CDN */}
<Script
src="https://cdn.tailwindcss.com"
strategy="beforeInteractive"
/>
<Navigation />
<div className="flex-1 pt-16 relative"> {/* Added relative for positioning */}
<AnimatePresence mode="wait" initial={false}>
{isClient && (
<motion.div
key={pathname}
initial="hidden"
animate="visible"
exit="exit"
variants={pageVariants}
className="min-h-screen"
>
{children}
</motion.div>
)}
</AnimatePresence>
{/* Falling petals overlay - appears on all pages */}
<FallingPetals count={40} />
</div>
<Footer />
</body>
</html>
);
}
@@ -1,164 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { ArrowRightIcon, SparklesIcon, Code2Icon, BrainIcon } from '@/components/icons/TetIcons';
import Link from 'next/link';
import ScrollReveal from '@/components/animations/ScrollReveal';
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.3,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6 },
},
};
export default function HomePage() {
return (
<main className="relative min-h-screen flex items-center justify-center overflow-hidden">
{/* Decorative border patterns */}
<div className="absolute inset-0 pointer-events-none">
{/* Top border - traditional cloud pattern */}
<div className="absolute top-0 left-0 right-0 h-4 bg-gradient-to-r from-[var(--tet-red)] via-[var(--tet-gold)] to-[var(--tet-red)]" />
{/* Corner decorations */}
<div className="absolute top-4 left-4 w-16 h-16 border-t-4 border-l-4 border-[var(--tet-red)]" />
<div className="absolute top-4 right-4 w-16 h-16 border-t-4 border-r-4 border-[var(--tet-red)]" />
<div className="absolute bottom-4 left-4 w-16 h-16 border-b-4 border-l-4 border-[var(--tet-red)]" />
<div className="absolute bottom-4 right-4 w-16 h-16 border-b-4 border-r-4 border-[var(--tet-red)]" />
</div>
{/* Main content */}
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="relative z-10 max-w-6xl mx-auto px-6 py-24 text-center"
>
{/* Greeting in Traditional Vietnamese style */}
<motion.div variants={itemVariants} className="mb-6">
<span className="inline-block px-6 py-2 bg-[var(--tet-red)] text-white rounded-full text-sm font-semibold tracking-wider uppercase">
🎊 Chúc Mừng Năm Mới 🎊
</span>
</motion.div>
{/* Test agent completion badge */}
<motion.div variants={itemVariants} className="mb-4">
<span className="inline-flex items-center gap-2 px-4 py-1.5 bg-green-100 text-green-800 rounded-full text-xs font-medium border border-green-200">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
Test Agent Task Completed
</span>
</motion.div>
{/* Name with golden glow */}
<motion.h1
variants={itemVariants}
className="text-5xl md:text-8xl font-bold mb-4 bg-[var(--tet-gold-gradient)] bg-clip-text text-transparent text-glow"
>
Nguyễn Ngọc Trí
</motion.h1>
{/* Role with red accent */}
<motion.h2
variants={itemVariants}
className="text-2xl md:text-4xl font-semibold mb-8 text-[var(--tet-red)]"
>
AI Engineer
</motion.h2>
{/* Bio */}
<motion.p
variants={itemVariants}
className="text-lg md:text-xl max-w-3xl mx-auto mb-12 leading-relaxed text-gray-700"
>
Passionate about building intelligent systems that bridge the gap between AI and real-world business problems.
Currently researching AI-assisted solutions at <span className="font-bold text-[var(--tet-red)]">SmartGift Solution</span>.
</motion.p>
{/* Quick stats */}
<motion.div
variants={itemVariants}
className="grid grid-cols-2 md:grid-cols-4 gap-6 mb-12 max-w-4xl mx-auto"
>
{[
{ icon: BrainIcon, label: 'AI/ML', value: 'Expert' },
{ icon: Code2Icon, label: 'Experience', value: '3+ Years' },
{ icon: SparklesIcon, label: 'Projects', value: '20+' },
{ icon: () => (
<span className="text-2xl">🎓</span>
), label: 'Education', value: 'Valedictorian' },
].map((stat, index) => (
<motion.div
key={index}
whileHover={{ scale: 1.05 }}
className="flex flex-col items-center p-6 bg-white/80 backdrop-blur-sm rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg card-hover"
>
<stat.icon className="w-8 h-8 text-[var(--tet-red)] mb-2" />
<span className="text-3xl font-bold text-[var(--tet-gold)]">{stat.value}</span>
<span className="text-sm text-gray-600 mt-1">{stat.label}</span>
</motion.div>
))}
</motion.div>
{/* CTA buttons */}
<motion.div
variants={itemVariants}
className="flex flex-col sm:flex-row gap-4 justify-center"
>
<Link
href="/about"
className="group inline-flex items-center justify-center px-8 py-4 bg-[var(--tet-red)] text-white rounded-full font-semibold text-lg hover:bg-[var(--tet-red-dark)] transition-all duration-300 shadow-lg hover:shadow-xl hover:scale-105"
>
Learn More
<ArrowRightIcon className="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform" />
</Link>
<Link
href="/projects"
className="inline-flex items-center justify-center px-8 py-4 border-2 border-[var(--tet-red)] text-[var(--tet-red)] rounded-full font-semibold text-lg hover:bg-[var(--tet-red)] hover:text-white transition-all duration-300"
>
View Projects
</Link>
<Link
href="/contact"
className="inline-flex items-center justify-center px-8 py-4 bg-[var(--tet-gold)] text-[var(--tet-dark)] rounded-full font-semibold text-lg hover:bg-[var(--tet-gold-light)] transition-all duration-300"
>
Get in Touch
</Link>
</motion.div>
</motion.div>
{/* Floating lantern decoration */}
<motion.div
animate={{ y: [0, -20, 0] }}
transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }}
className="absolute bottom-10 left-10 text-4xl hidden lg:block"
>
🏮
</motion.div>
<motion.div
animate={{ y: [0, -15, 0] }}
transition={{ duration: 2.5, repeat: Infinity, ease: 'easeInOut', delay: 0.5 }}
className="absolute bottom-10 right-10 text-4xl hidden lg:block"
>
🧧
</motion.div>
</main>
);
}
@@ -1,105 +0,0 @@
'use client';
import { motion } from 'framer-motion';
import { ExternalLinkIcon, GithubIcon, FolderOpenIcon, TagIcon } from '@/components/icons/TetIcons';
import ScrollReveal from '@/components/animations/ScrollReveal';
const projects = [
return (
<main className="min-h-screen py-20 px-4 sm:px-6 lg:px-8 bg-[var(--tet-cream)]">
<div className="max-w-7xl mx-auto">
{/* Page Header */}
<ScrollReveal variant="fadeInUp">
<div className="text-center mb-16">
<h1 className="text-5xl font-bold text-[var(--tet-red)] mb-4">Projects</h1>
<div className="h-1 w-32 bg-[var(--tet-gold)] mx-auto rounded-full mb-4" />
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
A showcase of AI, computer vision, and automation projects that demonstrate my expertise in applied AI and full-stack development.
</p>
</div>
</ScrollReveal>
{/* Projects Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{projects.map((project, index) => (
<ScrollReveal key={project.id} variant="fadeInUp" delay={index * 0.1}>
<motion.div
whileHover={{ y: -8 }}
className="bg-white rounded-2xl border-2 border-[var(--tet-red)]/20 shadow-lg overflow-hidden card-hover group"
>
{/* Card Header with decorative pattern */}
<div className={`h-3 bg-gradient-to-r from-${project.color === 'tet-red' ? '[var(--tet-red)]' : '[var(--tet-gold)]'} to-${project.color === 'tet-red' ? '[var(--tet-red-dark)]' : '[#FFA500]'}`} />
<div className="p-6">
{/* Icon and year */}
<div className="flex justify-between items-start mb-4">
<div className={`p-3 rounded-xl bg-${project.color}/10`}>
<FolderOpenIcon className={`w-8 h-8 text-${project.color === 'tet-red' ? '[var(--tet-red)]' : '[var(--tet-gold)]'}`} />
</div>
<span className="text-sm font-medium text-gray-500">{project.year}</span>
</div>
{/* Title */}
<h3 className="text-xl font-bold text-gray-800 mb-3 line-clamp-2 min-h-[3.5rem]">
{project.title}
</h3>
{/* Description */}
<p className="text-gray-600 mb-6 text-sm leading-relaxed line-clamp-3">
{project.description}
</p>
{/* Tags */}
<div className="flex flex-wrap gap-2 mb-6">
{project.tags.map(tag => (
<span
key={tag}
className="inline-flex items-center gap-1 px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-xs"
>
<TagIcon className="w-3 h-3" />
{tag}
</span>
))}
</div>
{/* Links */}
<div className="flex gap-3">
<a
href={project.link}
className="flex-1 inline-flex items-center justify-center gap-2 px-4 py-2 bg-[var(--tet-red)] text-white rounded-lg text-sm font-medium hover:bg-[var(--tet-red-dark)] transition-colors"
>
<ExternalLinkIcon className="w-4 h-4" />
View
</a>
<a
href={project.github}
className="inline-flex items-center justify-center gap-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg text-sm hover:bg-gray-50 transition-colors"
>
<GithubIcon className="w-4 h-4" />
Code
</a>
</div>
</div>
</motion.div>
</ScrollReveal>
))}
</div>
{/* More projects placeholder */}
<ScrollReveal variant="fadeInUp" delay={0.6}>
<div className="mt-16 text-center">
<p className="text-gray-600 mb-4">
Want to see more of my work or discuss a collaboration?
</p>
<a
href="/contact"
className="inline-flex items-center gap-2 px-8 py-4 bg-[var(--tet-gold)] text-[var(--tet-dark)] rounded-full font-semibold hover:bg-[var(--tet-gold-light)] transition-all duration-300"
>
Get in Touch
</a>
</div>
</ScrollReveal>
</div>
</main>
);
}
@@ -1,124 +0,0 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface Petal {
id: number;
x: number;
size: number;
rotation: number;
duration: number;
delay: number;
type: 'cherry' | 'plum';
}
interface FallingPetalsProps {
count?: number;
enabled?: boolean;
}
export default function FallingPetals({ count = 40, enabled = true }: FallingPetalsProps) {
const [petals, setPetals] = useState<Petal[]>([]);
const [isVisible, setIsVisible] = useState(true);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!enabled) return;
// Generate initial petals
const initialPetals: Petal[] = Array.from({ length: count }, (_, i) => ({
id: i,
x: Math.random() * 100,
size: 8 + Math.random() * 16,
rotation: Math.random() * 360,
duration: 8 + Math.random() * 12,
delay: Math.random() * 5,
type: Math.random() > 0.5 ? 'cherry' : 'plum',
}));
setPetals(initialPetals);
// Recycle petals: regenerate when they reach bottom
const interval = setInterval(() => {
setPetals(prev => prev.map(petal => ({
...petal,
id: petal.id + count, // new id to trigger animation
x: Math.random() * 100,
rotation: Math.random() * 360,
delay: 0,
})));
}, 10000);
return () => clearInterval(interval);
}, [count, enabled]);
const getPetalColor = (type: 'cherry' | 'plum') => {
return type === 'cherry'
? ['#FFB7C5', '#FF69B4', '#FF1493', '#DB7093'] // Cherry blossom pink
: ['#FFD700', '#FFA500', '#FF8C00', '#FFCC00']; // Plum blossom gold
};
const getPetalSVG = (type: 'cherry' | 'plum', color: string) => {
// Simple petal shape SVG
return (
<svg width="100%" height="100%" viewBox="0 0 50 50">
<path
d="M25,5 C20,15 10,20 10,30 C10,40 25,45 25,45 C25,45 40,40 40,30 C40,20 30,15 25,5 Z"
fill={color}
opacity="0.8"
/>
</svg>
);
};
if (!enabled) return null;
return (
<div
ref={containerRef}
className="fixed inset-0 pointer-events-none z-0 overflow-hidden"
aria-hidden="true"
>
<AnimatePresence>
{petals.map((petal) => {
const colors = getPetalColor(petal.type);
const color = colors[Math.floor(Math.random() * colors.length)];
return (
<motion.div
key={petal.id}
className="absolute"
style={{
left: `${petal.x}%`,
width: petal.size,
height: petal.size,
}}
initial={{
y: -50,
rotate: petal.rotation,
opacity: 0,
}}
animate={{
y: '110vh',
rotate: petal.rotation + 360,
opacity: [0, 0.9, 0.9, 0],
}}
transition={{
duration: petal.duration,
delay: petal.delay,
ease: 'linear',
opacity: {
duration: petal.duration,
times: [0, 0.1, 0.9, 1],
},
}}
>
{getPetalSVG(petal.type, color)}
</motion.div>
);
})}
</AnimatePresence>
</div>
);
}
@@ -1,102 +0,0 @@
'use client';
import React from 'react';
import { motion, useInView, Variant } from 'framer-motion';
interface ScrollRevealProps {
children: ReactNode;
className?: string;
delay?: number;
duration?: number;
once?: boolean;
offset?: number;
amount?: number;
}
const fadeInUp = {
hidden: { opacity: 0, y: 60 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }
},
};
const fadeIn = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: 0.6 }
},
};
const slideInLeft = {
hidden: { opacity: 0, x: -60 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }
},
};
const slideInRight = {
hidden: { opacity: 0, x: 60 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: [0.25, 0.1, 0.25, 1] }
},
};
const scaleIn = {
hidden: { opacity: 0, scale: 0.9 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 0.5 }
},
};
const variants = {
fadeInUp,
fadeIn,
slideInLeft,
slideInRight,
scaleIn,
};
export default function ScrollReveal({
children,
className = '',
delay = 0,
duration,
once = true,
offset = 0,
amount = 0.3,
variant = 'fadeInUp',
}: ScrollRevealProps & { variant?: keyof typeof variants }) {
const ref = React.useRef(null);
const isInView = useInView(ref, {
once,
amount,
margin: `0px ${offset}px 0px ${offset}px`,
});
const selectedVariant = variants[variant];
return (
<motion.div
ref={ref}
className={className}
initial="hidden"
animate={isInView ? 'visible' : 'hidden'}
variants={selectedVariant}
transition={{
delay,
...(duration && { duration }),
}}
>
{children}
</motion.div>
);
}
@@ -1,467 +0,0 @@
import React from 'react';
interface IconProps {
className?: string;
size?: number;
}
export const GithubIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 4-3 4-3.5" />
<path d="M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
<circle cx="12" cy="11" r="3" />
<path d="M12 16v5" />
<path d="M8 16h8" />
</svg>
);
export const LinkedInIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6Z" />
<rect width="4" height="12" x="2" y="9" />
<circle cx="4" cy="4" r="2" />
</svg>
);
export const MailIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<rect width="20" height="16" x="2" y="4" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
);
export const HeartIcon: React.FC<IconProps> = ({ className = "w-4 h-4", size = 20 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" />
</svg>
);
export const ExternalLinkIcon: React.FC<IconProps> = ({ className = "w-4 h-4", size = 20 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
<polyline points="15 3 21 3 21 9" />
<line x1="10" x2="21" y1="14" y2="3" />
</svg>
);
export const FolderOpenIcon: React.FC<IconProps> = ({ className = "w-8 h-8", size = 28 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6" />
<path d="M4 5v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2Z" />
<path d="M4 10h16" />
<path d="M4 18h16" />
</svg>
);
export const TagIcon: React.FC<IconProps> = ({ className = "w-3 h-3", size = 16 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M12 2H2l2.5 7.5L7 14l-2.5 2L2 22h20l-2.5-6.5L17 14l-2.5-2-2.5 2.5L22 2H12z" />
</svg>
);
export const PhoneIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
</svg>
);
export const MapPinIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
<circle cx="12" cy="10" r="3" />
</svg>
);
export const SendIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="m22 2-7 20-4-9-9-4Z" />
<path d="M22 2 11 13" />
</svg>
);
export const ArrowRightIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
);
export const GraduationCapIcon: React.FC<IconProps> = ({ className = "w-8 h-8", size = 28 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z" />
<path d="M22 10v8" />
<path d="M2 18.08V10" />
<path d="M12 22v-9" />
<path d="M12 13V3" />
</svg>
);
export const AwardIcon: React.FC<IconProps> = ({ className = "w-6 h-6", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<circle cx="12" cy="8" r="6" />
<path d="M15.4 8.6a2 2 0 1 0-2.8-2.8" />
<path d="M8.6 8.6a2 2 0 1 0-2.8 2.8" />
<path d="M12 18v2" />
<path d="M12 2v2" />
<path d="M6 12H2" />
<path d="M22 12h-4" />
</svg>
);
export const LanguagesIcon: React.FC<IconProps> = ({ className = "w-6 h-6", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M4 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M8 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M12 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M16 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M20 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M7 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M11 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M4 15a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M8 15a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M12 15a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M16 15a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
<path d="M20 15a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" />
</svg>
);
export const BriefcaseIcon: React.FC<IconProps> = ({ className = "w-8 h-8", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<rect width="20" height="14" x="2" y="7" rx="2" ry="2" />
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" />
</svg>
);
export const CalendarIcon: React.FC<IconProps> = ({ className = "w-4 h-4", size = 20 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
<line x1="16" x2="16" y1="2" y2="6" />
<line x1="8" x2="8" y1="2" y2="6" />
<line x1="3" x2="21" y1="10" y2="10" />
</svg>
);
export const HomeIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
);
export const UserIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
);
export const FolderIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6" />
<path d="M4 5v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2Z" />
</svg>
);
export const MailSendIcon: React.FC<IconProps> = ({ className = "w-5 h-5", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<rect width="20" height="16" x="2" y="4" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
);
export const BrainIcon: React.FC<IconProps> = ({ className = "w-8 h-8", size = 32 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15A2.5 2.5 0 0 1 9.5 22A2.5 2.5 0 0 1 7 19.5v-15A2.5 2.5 0 0 1 9.5 2Z" />
<path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5V7a2.5 2.5 0 0 0-2.5-2.5Z" />
<path d="M9 8h6" />
<path d="M9 12h6" />
<path d="M9 16h4" />
</svg>
);
export const Code2Icon: React.FC<IconProps> = ({ className = "w-8 h-8", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
</svg>
);
export const SparklesIcon: React.FC<IconProps> = ({ className = "w-8 h-8", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
<path d="M5 3v4" />
<path d="M19 17v4" />
<path d="M3 5h4" />
<path d="M17 19h4" />
</svg>
);
export const AwardStarIcon: React.FC<IconProps> = ({ className = "text-2xl", size = 24 }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
</svg>
);
@@ -1,57 +0,0 @@
import { GithubIcon, LinkedInIcon, MailIcon, HeartIcon } from '@/components/icons/TetIcons';
export default function Footer() {
const currentYear = new Date().getFullYear();
const socialLinks = [
{ href: 'https://github.com', icon: GithubIcon, label: 'GitHub' },
{ href: 'https://linkedin.com/in/nguyen-ngoc-tri-vi', icon: LinkedInIcon, label: 'LinkedIn' },
{ href: 'mailto:nntrivi2001@gmail.com', icon: MailIcon, label: 'Email' },
];
return (
<footer className="bg-[var(--tet-red)] text-white py-12 mt-auto">
<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 space-y-4 md:space-y-0">
{/* Logo and tagline */}
<div className="text-center md:text-left">
<h3 className="text-2xl font-bold mb-2 text-[var(--tet-gold)]">
Trí
</h3>
<p className="text-sm opacity-90">
AI Engineer & Creative Developer
</p>
</div>
{/* Social links */}
<div className="flex space-x-4">
{socialLinks.map((social) => {
const Icon = social.icon;
return (
<a
key={social.label}
href={social.href}
target="_blank"
rel="noopener noreferrer"
className="p-2 bg-white/10 rounded-full hover:bg-white/20 transition-all duration-300 hover:scale-110"
aria-label={social.label}
>
<Icon className="w-5 h-5" />
</a>
);
})}
</div>
</div>
{/* Copyright */}
<div className="mt-8 pt-8 border-t border-white/20 text-center">
<p className="text-sm opacity-80 flex items-center justify-center gap-2">
© {currentYear} Nguyễn Ngọc Trí . Made with{' '}
<HeartIcon className="w-4 h-4 fill-[var(--tet-gold)] text-[var(--tet-gold)]" />
and Vietnamese Tet spirit
</p>
</div>
</div>
</footer>
);
}
@@ -1,92 +0,0 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { motion } from 'framer-motion';
import { HomeIcon, UserIcon, BriefcaseIcon, FolderOpenIcon, MailIcon } from '@/components/icons/TetIcons';
const navItems = [
{ href: '/', label: 'Home', icon: HomeIcon },
{ href: '/about', label: 'About', icon: UserIcon },
{ href: '/experience', label: 'Experience', icon: BriefcaseIcon },
{ href: '/projects', label: 'Projects', icon: FolderOpenIcon },
{ href: '/contact', label: 'Contact', icon: MailIcon },
];
export default function Navigation() {
const pathname = usePathname();
return (
<nav className="fixed top-0 left-0 right-0 z-50 bg-[var(--tet-cream)]/95 backdrop-blur-md shadow-lg border-b-2 border-[var(--tet-red)]">
<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="flex items-center space-x-2">
<motion.div
whileHover={{ rotate: 10 }}
className="text-2xl font-bold bg-[var(--tet-red-gradient)] bg-clip-text text-transparent"
>
Trí
</motion.div>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex space-x-1">
{navItems.map((item) => {
const isActive = pathname === item.href;
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="relative px-4 py-2 rounded-lg transition-all duration-300"
>
<div className="flex items-center space-x-2">
<Icon className="w-4 h-4" />
<span className="font-medium">{item.label}</span>
</div>
{/* Active indicator */}
{isActive && (
<motion.div
layoutId="nav-indicator"
className="absolute inset-0 bg-[var(--tet-red)]/10 rounded-lg border-2 border-[var(--tet-red)]"
initial={false}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
)}
{/* Hover effect */}
{!isActive && (
<motion.div
className="absolute inset-0 bg-[var(--tet-gold)]/10 rounded-lg"
initial={{ opacity: 0 }}
whileHover={{ opacity: 1 }}
transition={{ duration: 0.2 }}
/>
)}
</Link>
);
})}
</div>
{/* Mobile menu button - simplified */}
<div className="md:hidden">
<div className="flex space-x-2">
{navItems.slice(0, 3).map((item) => (
<Link
key={item.href}
href={item.href}
className="p-2 rounded-lg hover:bg-[var(--tet-red)]/10 transition"
>
<item.icon className="w-5 h-5 text-[var(--tet-red)]" />
</Link>
))}
</div>
</div>
</div>
</div>
</nav>
);
}
@@ -1,43 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts",
".next/dev/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}