# Cổng số hóa di sản VR360 Xã Như Quỳnh — Kế hoạch triển khai

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Dựng cổng thông tin số hóa di sản cho Xã Như Quỳnh (Hưng Yên) trong `phy-vr360`, port engine + design system từ `pdc-vr360`, hoàn thiện tour VR360 Chùa Thái Lạc hoạt động đầy đủ.

**Architecture:** `phy-vr360` (Next.js 16, App Router, `src/app/`) tái sử dụng nguyên design system (`globals.css`), các component landing và engine `VirtualTour.tsx` của `pdc-vr360`. Một file `content.ts` là nguồn dữ liệu trung tâm (15 di tích, điều hướng, tour). Engine được tổng quát hóa để chạy bằng config tour động; thêm config Chùa Thái Lạc từ 26 ảnh panorama sẵn có.

**Tech Stack:** Next.js 16, React 19, TypeScript, Tailwind v4, Three.js, framer-motion, lucide-react, @paper-design/shaders-react.

**Quy ước kiểm thử (theo pattern codebase — không có unit test):** mỗi task verify bằng `npm run lint` và `npm run build` (build chạy type-check). Các task UI/tour thêm bước kiểm tra trực quan bằng `npm run dev` rồi mở URL. "PASS" = build + lint không lỗi và quan sát đúng mô tả.

**Nguồn tham chiếu:** các file gốc nằm tại `../pdc-vr360/app/...` (cùng workspace `d:\Code\vr360`). Khi task nói "copy từ pdc", lấy nguyên nội dung file gốc rồi áp các chỉnh sửa được liệt kê.

**⚠️ Next.js 16:** `pdc-vr360/AGENTS.md` cảnh báo bản Next.js này có breaking changes. Trước khi sửa code routing/layout khác kiến thức cũ, đọc guide trong `node_modules/next/dist/docs/`.

**Đường dẫn alias:** cả hai dự án dùng `@/app/...`. Ở `phy-vr360`, `@/*` → `./src/*`, nên `@/app/content` = `src/app/content.ts`. Giữ nguyên kiểu import `@/app/...` khi copy.

---

## PHASE 1 — Nền tảng (dependencies, design system, layout, assets)

### Task 1: Thêm dependencies còn thiếu

**Files:**
- Modify: `phy-vr360/package.json`

- [ ] **Step 1: Cài các package khớp pdc-vr360**

Chạy tại thư mục `phy-vr360`:

```bash
cd d:/Code/vr360/phy-vr360
npm install three@^0.184.0 framer-motion@^12.40.0 lucide-react@^1.16.0 @paper-design/shaders-react@^0.0.76 lottie-react@^2.4.1
npm install -D @types/three@^0.184.1
```

- [ ] **Step 2: Verify cài đặt**

Run: `npm ls three framer-motion lucide-react @paper-design/shaders-react`
Expected: liệt kê đủ 4 package, không có `UNMET DEPENDENCY`.

- [ ] **Step 3: Commit**

```bash
git add package.json package-lock.json
git commit -m "chore: thêm dependencies cho VR360 Như Quỳnh (three, framer-motion, lucide, shaders)"
```

---

### Task 2: Port design system globals.css

**Files:**
- Replace: `phy-vr360/src/app/globals.css` (copy từ `pdc-vr360/app/globals.css`, toàn bộ 1380 dòng)

- [ ] **Step 1: Copy nguyên file**

```bash
cp d:/Code/vr360/pdc-vr360/app/globals.css d:/Code/vr360/phy-vr360/src/app/globals.css
```

Không sửa nội dung — đây là hệ token + class `public-*` cần giữ y hệt để đảm bảo palette xanh–vàng di sản và chất lượng đã kiểm chứng.

- [ ] **Step 2: Verify build CSS**

Run: `npm run build`
Expected: build PASS (có thể còn lỗi do `page.tsx` mặc định vẫn dùng class cũ — chấp nhận, sẽ thay ở Task 5). Nếu lỗi chỉ liên quan `globals.css` (cú pháp Tailwind) thì phải sửa; lỗi khác bỏ qua ở bước này.

- [ ] **Step 3: Commit**

```bash
git add src/app/globals.css
git commit -m "feat: port design system globals.css (palette di sản, class public-*)"
```

---

### Task 3: Cập nhật layout gốc (font, lang, metadata)

**Files:**
- Modify: `phy-vr360/src/app/layout.tsx`

- [ ] **Step 1: Thay toàn bộ nội dung layout**

Ghi đè `phy-vr360/src/app/layout.tsx` bằng:

```tsx
import type { Metadata, Viewport } from "next";
import { Noto_Sans } from "next/font/google";
import "./globals.css";

const notoSans = Noto_Sans({
  subsets: ["latin", "vietnamese"],
  variable: "--font-noto-sans",
  weight: ["400", "500", "600", "700", "800"],
});

export const metadata: Metadata = {
  title: "VR360 Di sản Xã Như Quỳnh",
  description:
    "Cổng số hóa di tích, không gian văn hóa và giá trị truyền thống của Xã Như Quỳnh, tỉnh Hưng Yên bằng công nghệ tham quan thực tế ảo VR360.",
};

export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="vi" className={`${notoSans.variable} h-full antialiased`}>
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}
```

- [ ] **Step 2: Verify**

Run: `npm run lint`
Expected: không lỗi ở `layout.tsx`.

- [ ] **Step 3: Commit**

```bash
git add src/app/layout.tsx
git commit -m "feat: layout gốc dùng Noto Sans tiếng Việt, metadata Như Quỳnh"
```

---

### Task 4: Copy assets (icon, modal)

**Files:**
- Create: `phy-vr360/public/modal/` (copy từ `pdc-vr360/public/modal/`)
- Verify: `phy-vr360/public/icon/` (đã có `flycam.png`, `hotspotelement.png`, `_ggMapPin.png`)

- [ ] **Step 1: Copy thư mục modal (nền welcome/info của tour)**

```bash
cp -r "d:/Code/vr360/pdc-vr360/public/modal" "d:/Code/vr360/phy-vr360/public/modal"
```

- [ ] **Step 2: Xác nhận icon đã đủ**

Run: `ls d:/Code/vr360/phy-vr360/public/icon`
Expected: `_ggMapPin.png  flycam.png  hotspotelement.png`

- [ ] **Step 3: Commit**

```bash
git add public/modal
git commit -m "feat: copy assets modal cho viewer VR360"
```

---

## PHASE 2 — Dữ liệu & các trang landing

### Task 5: Tạo `content.ts` — nguồn dữ liệu trung tâm

**Files:**
- Create: `phy-vr360/src/app/content.ts`

Định nghĩa các export mà component port sẽ dùng. `HeritageSite` giữ các field component cũ cần (`id`, `image`, `type`, `status`, `shortDescription`, `isAvailable`, `vrUrl`, `detailUrl`) và bổ sung field mới (`level`, `images`, `longDescription`, `location`).

- [ ] **Step 1: Viết file content.ts**

Tạo `phy-vr360/src/app/content.ts`:

```ts
const imgBase = "/images/Đình Thái Lạc"; // thư mục chứa 26 ảnh Chùa Thái Lạc
export const img = (file: string) => encodeURI(`${imgBase}/${file}`);

export const commune = {
  name: "Xã Như Quỳnh",
  province: "tỉnh Hưng Yên",
  fullName: "Xã Như Quỳnh, tỉnh Hưng Yên",
  intro:
    "Vùng đất cổ giàu truyền thống văn hiến và khoa bảng, nơi lưu giữ hệ thống di tích đình, chùa, đền, văn miếu trải dài nhiều thế kỷ.",
};

export const activeTour = {
  title: "Chùa Thái Lạc (Pháp Vân tự)",
  subtitle: "Di tích quốc gia — chạm khắc gỗ thời Trần",
  href: "/tour/chua-thai-lac",
  heroImage: img("Chính Diện Cổng Chùa.jpg"),
  coverImage: img("Chính Sảnh.jpg"),
  gateImage: img("Chính Diện Cổng Chùa.jpg"),
  shrineImage: img("Chính Điện 3.jpg"),
  sceneCount: "26",
  duration: "15-20 phút",
};

export const navigation = [
  { href: "/", label: "Trang chủ" },
  { href: "/gioi-thieu", label: "Giới thiệu" },
  { href: "/di-tich", label: "Di tích" },
  { href: "/lien-he", label: "Liên hệ" },
];

export type HeritageLevel = "Cấp quốc gia" | "Cấp tỉnh";

export type HeritageSite = {
  id: string; // = slug, dùng cho /di-tich/[slug] và anchor
  name: string;
  type: string; // "Chùa" | "Đình" | "Đền" | "Văn miếu" | "Di tích khác"
  level: HeritageLevel;
  status: "Đang mở tour" | "Đang bổ sung tư liệu";
  shortDescription: string;
  longDescription?: string[];
  image: string; // ảnh đại diện
  images?: string[];
  vrUrl?: string;
  detailUrl: string;
  isAvailable: boolean;
  location?: string;
};

export const heritageSites: HeritageSite[] = [
  {
    id: "chua-thai-lac",
    name: "Chùa Thái Lạc (Pháp Vân tự)",
    type: "Chùa",
    level: "Cấp quốc gia",
    status: "Đang mở tour",
    shortDescription:
      "Ngôi chùa cổ thờ Pháp Vân trong hệ Tứ Pháp, nổi tiếng với những mảng chạm khắc gỗ thời Trần (thế kỷ 14) thuộc hàng quý hiếm bậc nhất Việt Nam.",
    longDescription: [
      "Chùa Thái Lạc, tên chữ là Pháp Vân tự, là một trong những ngôi chùa cổ tiêu biểu của vùng Như Quỳnh, thờ Pháp Vân — vị thần Mây trong tín ngưỡng Tứ Pháp của cư dân nông nghiệp đồng bằng Bắc Bộ.",
      "Giá trị đặc biệt của di tích nằm ở hệ thống ván chạm khắc gỗ có niên đại thời Trần (thế kỷ 14), khắc họa các nhạc công, vũ nữ, hình tượng Phật giáo với kỹ thuật điêu luyện — nguồn tư liệu quý cho nghiên cứu mỹ thuật cổ Việt Nam.",
      "Không gian chùa gồm cổng, sân, tiền đường, thượng điện cùng các ban thờ và cảnh quan vườn, ao, được số hóa đầy đủ trong tour VR360.",
    ],
    image: img("Chính Diện Cổng Chùa.jpg"),
    vrUrl: "/tour/chua-thai-lac",
    detailUrl: "/di-tich/chua-thai-lac",
    isAvailable: true,
    location: "Xã Như Quỳnh, tỉnh Hưng Yên",
  },
  // --- 8 di tích quốc gia còn lại (mẫu, mô tả ngắn) ---
  nationalStub("den-ghenh", "Đền Ghênh (Đền Ỷ Lan)", "Đền",
    "Đền thờ Nguyên phi Ỷ Lan, gắn với truyền thống tôn vinh người phụ nữ có công với dân với nước."),
  nationalStub("tu-vu-ho-truong", "Từ Vũ họ Trương", "Di tích khác",
    "Từ đường dòng họ Trương, di tích phản ánh truyền thống dòng tộc và tín ngưỡng thờ tổ tiên của địa phương."),
  nationalStub("bai-da-den-tu-vu", "Bãi đá đền Từ Vũ", "Di tích khác",
    "Cụm di tích khảo cổ — kiến trúc đá gắn với đền Từ Vũ, mang giá trị lịch sử và văn hóa lâu đời."),
  nationalStub("dinh-at", "Đình Ất", "Đình",
    "Đình làng cổ, không gian sinh hoạt tín ngưỡng và cộng đồng tiêu biểu của làng quê Như Quỳnh."),
  nationalStub("chua-hanh-lac", "Chùa Hành Lạc", "Chùa",
    "Ngôi chùa làng cổ kính, trung tâm sinh hoạt Phật giáo và văn hóa tâm linh của cộng đồng."),
  nationalStub("dinh-hanh-lac", "Đình Hành Lạc", "Đình",
    "Đình làng gắn với tín ngưỡng thờ Thành hoàng và lễ hội truyền thống của địa phương."),
  nationalStub("chua-ong", "Chùa Ông", "Chùa",
    "Di tích Phật giáo cổ, lưu giữ nhiều giá trị kiến trúc và hiện vật tín ngưỡng."),
  nationalStub("chua-vinh-thai", "Chùa Vĩnh Thái", "Chùa",
    "Ngôi chùa cổ mang đậm dấu ấn kiến trúc và tín ngưỡng truyền thống vùng Như Quỳnh."),
  // --- 6 di tích cấp tỉnh (mẫu) ---
  provincialStub("dinh-le-xa", "Đình Lê Xá", "Đình",
    "Đình làng Lê Xá, không gian thờ tự và sinh hoạt cộng đồng giàu giá trị văn hóa."),
  provincialStub("dinh-ngo-xuyen", "Đình Ngô Xuyên", "Đình",
    "Đình làng Ngô Xuyên gắn với truyền thống tín ngưỡng và lễ hội địa phương."),
  provincialStub("chua-tu-kinh", "Chùa Từ Kính", "Chùa",
    "Ngôi chùa cổ, điểm sinh hoạt Phật giáo và văn hóa tâm linh của cộng đồng."),
  provincialStub("cung-chi-nguyen", "Cung Chí Nguyên", "Di tích khác",
    "Di tích gắn với nhà văn Thạch Lam — nơi khơi nguồn cảm hứng cho truyện ngắn \"Dưới bóng hoàng lan\"."),
  provincialStub("van-mieu-nhu-quynh", "Văn miếu Như Quỳnh", "Văn miếu",
    "Văn miếu tôn vinh truyền thống hiếu học và khoa bảng của vùng đất Như Quỳnh."),
  provincialStub("dau-tich-chua-la", "Dấu tích Chùa Lá", "Chùa",
    "Dấu tích ngôi chùa lớn, nổi tiếng trước Cách mạng tháng Tám, mang giá trị lịch sử sâu sắc."),
];

function nationalStub(id: string, name: string, type: string, desc: string): HeritageSite {
  return stub(id, name, type, "Cấp quốc gia", desc);
}
function provincialStub(id: string, name: string, type: string, desc: string): HeritageSite {
  return stub(id, name, type, "Cấp tỉnh", desc);
}
function stub(id: string, name: string, type: string, level: HeritageLevel, desc: string): HeritageSite {
  return {
    id,
    name,
    type,
    level,
    status: "Đang bổ sung tư liệu",
    shortDescription: desc,
    image: img("Chính Sảnh.jpg"), // ảnh tạm dùng chung cho các di tích chưa có tư liệu
    detailUrl: `/di-tich/${id}`,
    isAvailable: false,
    location: "Xã Như Quỳnh, tỉnh Hưng Yên",
  };
}
```

> Lưu ý: `function` khai báo dạng hoisted nên dùng được trước khi định nghĩa trong cùng module — mảng `heritageSites` gọi `nationalStub`/`provincialStub` hợp lệ.

- [ ] **Step 2: Verify type-check**

Run: `npx tsc --noEmit`
Expected: không lỗi trong `content.ts`.

- [ ] **Step 3: Commit**

```bash
git add src/app/content.ts
git commit -m "feat: content.ts — dữ liệu 15 di tích Như Quỳnh + cấu hình tour"
```

---

### Task 6: Port component nền hero (shader + panorama)

**Files:**
- Create: `phy-vr360/src/app/components/ui/hero-shader-background.tsx`
- Create: `phy-vr360/src/app/components/ui/hero-panorama-background.tsx`

- [ ] **Step 1: Copy 2 file nền hero nguyên trạng**

```bash
mkdir -p d:/Code/vr360/phy-vr360/src/app/components/ui
cp "d:/Code/vr360/pdc-vr360/app/components/ui/hero-shader-background.tsx" "d:/Code/vr360/phy-vr360/src/app/components/ui/hero-shader-background.tsx"
cp "d:/Code/vr360/pdc-vr360/app/components/ui/hero-panorama-background.tsx" "d:/Code/vr360/phy-vr360/src/app/components/ui/hero-panorama-background.tsx"
```

Hai file dùng prop `imageSrc` động và import `@/app/components/ui/...` — tương thích phy-vr360, không cần sửa.

- [ ] **Step 2: Verify type-check**

Run: `npx tsc --noEmit`
Expected: không lỗi ở 2 file này (dependency `@paper-design/shaders-react` đã cài ở Task 1).

- [ ] **Step 3: Commit**

```bash
git add src/app/components/ui
git commit -m "feat: port component nền hero (shader + panorama)"
```

---

### Task 7: Port SiteHeader (thay branding Như Quỳnh)

**Files:**
- Create: `phy-vr360/src/app/components/landing/SiteHeader.tsx` (copy từ pdc + sửa)

- [ ] **Step 1: Copy file rồi áp các sửa đổi**

```bash
mkdir -p d:/Code/vr360/phy-vr360/src/app/components/landing
cp "d:/Code/vr360/pdc-vr360/app/components/landing/SiteHeader.tsx" "d:/Code/vr360/phy-vr360/src/app/components/landing/SiteHeader.tsx"
```

Sửa trong file vừa copy:

1. **Bỏ logo ảnh thiếu, thay bằng icon.** Xóa dòng:
```tsx
const dinhCongLogo = encodeURI("/images/định công.png");
```
Đổi import lucide thêm `Landmark`:
```tsx
import { Landmark, Menu, PlayCircle, X } from "lucide-react";
```
Bỏ `import Image from "next/image";` (không còn dùng).

2. **Thay khối logo `<span class="public-logo-mark">...<Image .../></span>`** bằng:
```tsx
<span className="public-logo-mark grid h-11 w-11 shrink-0 place-items-center overflow-hidden rounded-full border border-[rgb(142_95_11_/_0.24)] bg-[linear-gradient(135deg,rgb(255_255_255_/_0.94),rgb(232_219_176_/_0.74))] shadow-[0_10px_24px_rgb(37_75_54_/_0.1),inset_0_1px_0_rgb(255_255_255_/_0.92)]">
  <Landmark className="h-5 w-5 text-[var(--primary)]" strokeWidth={1.8} />
</span>
```

3. **Thay text thương hiệu:**
- `aria-label="VR360 Định Công"` → `aria-label="VR360 Di sản Như Quỳnh"`
- `VR360 Định Công` → `VR360 Như Quỳnh`
- `Không gian văn hóa số` → `Di sản văn hóa Xã Như Quỳnh`

- [ ] **Step 2: Verify**

Run: `npx tsc --noEmit`
Expected: không lỗi; không còn import `Image` thừa (lint sẽ báo nếu sót).

- [ ] **Step 3: Commit**

```bash
git add src/app/components/landing/SiteHeader.tsx
git commit -m "feat: port SiteHeader với branding Như Quỳnh"
```

---

### Task 8: Port SiteFooter + BackToTopButton (thay thông tin liên hệ)

**Files:**
- Create: `phy-vr360/src/app/components/landing/SiteFooter.tsx`
- Create: `phy-vr360/src/app/components/landing/BackToTopButton.tsx`

- [ ] **Step 1: Copy BackToTopButton nguyên trạng**

```bash
cp "d:/Code/vr360/pdc-vr360/app/components/landing/BackToTopButton.tsx" "d:/Code/vr360/phy-vr360/src/app/components/landing/BackToTopButton.tsx"
```

(Không chứa nội dung Định Công — giữ nguyên.)

- [ ] **Step 2: Copy SiteFooter rồi sửa nội dung**

```bash
cp "d:/Code/vr360/pdc-vr360/app/components/landing/SiteFooter.tsx" "d:/Code/vr360/phy-vr360/src/app/components/landing/SiteFooter.tsx"
```

Sửa trong file:
- `VR360 Định Công` → `VR360 Như Quỳnh`
- Câu mô tả → `Cổng số hóa di tích và không gian văn hóa Xã Như Quỳnh, tỉnh Hưng Yên bằng công nghệ VR360.`
- Địa chỉ `Số 1 ngõ 282 Kim Giang...` → `Xã Như Quỳnh, tỉnh Hưng Yên`
- Link `http://dinhcong.hanoi.gov.vn/` + nhãn `Cổng thông tin phường Định Công` → `#` tạm + nhãn `Cổng thông tin Xã Như Quỳnh` (kèm chú thích `Đang cập nhật` nếu cần)
- Bỏ block Facebook (`https://www.facebook.com/profile.php?id=...`) HOẶC đổi thành `#` với nhãn `Trang thông tin địa phương` — chọn bỏ để tránh link sai.
- `© 2026 VR360 Định Công.` → `© 2026 VR360 Di sản Xã Như Quỳnh.`

- [ ] **Step 3: Verify**

Run: `npx tsc --noEmit && npm run lint`
Expected: không lỗi; không còn import thừa (nếu bỏ block Facebook làm `ExternalLink` không dùng → xóa khỏi import).

- [ ] **Step 4: Commit**

```bash
git add src/app/components/landing/SiteFooter.tsx src/app/components/landing/BackToTopButton.tsx
git commit -m "feat: port SiteFooter (liên hệ Như Quỳnh) + BackToTopButton"
```

---

### Task 9: Port + sửa CulturalSpaceExplorer (lọc theo cấp di tích)

**Files:**
- Create: `phy-vr360/src/app/components/landing/CulturalSpaceExplorer.tsx`

Bộ lọc gốc lọc theo "Loại địa điểm" với các giá trị Định Công. Như Quỳnh cần lọc theo **Cấp (quốc gia/tỉnh)** + **Loại** phù hợp.

- [ ] **Step 1: Copy file**

```bash
cp "d:/Code/vr360/pdc-vr360/app/components/landing/CulturalSpaceExplorer.tsx" "d:/Code/vr360/phy-vr360/src/app/components/landing/CulturalSpaceExplorer.tsx"
```

- [ ] **Step 2: Sửa bộ lọc loại + thêm bộ lọc cấp**

Thay khối khai báo filter (dòng ~9-13) bằng:

```tsx
const statusFilters = ["Tất cả", "Đang mở tour", "Đang bổ sung tư liệu"] as const;
const levelFilters = ["Tất cả", "Cấp quốc gia", "Cấp tỉnh"] as const;
const typeFilters = ["Tất cả", "Đình", "Chùa", "Đền", "Văn miếu", "Di tích khác"] as const;

type StatusFilter = (typeof statusFilters)[number];
type LevelFilter = (typeof levelFilters)[number];
type TypeFilter = (typeof typeFilters)[number];
```

Trong component, thêm state `level` cạnh `status`/`type`:

```tsx
const [level, setLevel] = useState<LevelFilter>("Tất cả");
```

Thay logic `matchesType` trong `filteredSites` bằng (lọc theo cấp + loại chính xác hơn vì `site.level` và `site.type` đã chuẩn hóa trong content.ts):

```tsx
const matchesLevel = level === "Tất cả" || site.level === level;
const matchesType = type === "Tất cả" || site.type === type;
return matchesQuery && matchesStatus && matchesLevel && matchesType;
```

Trong JSX khối bộ lọc, thêm một `<FilterGroup>` cho cấp (đặt trước "Loại địa điểm"):

```tsx
<FilterGroup label="Cấp xếp hạng" options={levelFilters} value={level} onChange={setLevel} />
```

Cập nhật nút "Xóa bộ lọc" để reset thêm `setLevel("Tất cả")`.

Đổi grid bộ lọc `lg:grid-cols-[1fr_auto_auto]` → `lg:grid-cols-[1fr_auto_auto_auto]` để chứa 3 nhóm filter.

- [ ] **Step 3: Verify**

Run: `npx tsc --noEmit`
Expected: không lỗi.

- [ ] **Step 4: Commit**

```bash
git add src/app/components/landing/CulturalSpaceExplorer.tsx
git commit -m "feat: CulturalSpaceExplorer lọc theo cấp xếp hạng + loại di tích"
```

---

### Task 10: Trang chủ `/`

**Files:**
- Replace: `phy-vr360/src/app/page.tsx` (dựa trên `pdc-vr360/app/page.tsx`)

- [ ] **Step 1: Copy page.tsx của pdc rồi sửa nội dung**

```bash
cp "d:/Code/vr360/pdc-vr360/app/page.tsx" "d:/Code/vr360/phy-vr360/src/app/page.tsx"
```

Áp các sửa đổi:
1. **metadata:** title `Trang chủ | VR360 Như Quỳnh`; description về Xã Như Quỳnh.
2. **kicker/heading/giới thiệu** đổi "Định Công" → nội dung Như Quỳnh:
   - kicker: `VR360 số hóa di sản Xã Như Quỳnh`
   - h1: `Khám phá di sản văn hóa Xã Như Quỳnh qua trải nghiệm VR360`
   - đoạn mô tả: `Hành trình số hóa hệ thống di tích, không gian văn hóa và giá trị truyền thống của Xã Như Quỳnh, tỉnh Hưng Yên bằng công nghệ tham quan thực tế ảo 360 độ.`
3. **Ảnh hero** (`src="/images-tour/Đình Làng-Đền Thờ/12...jpg"`) → dùng dữ liệu: thay bằng `activeTour.coverImage`.
4. Khối "Giới thiệu nhanh" / "Dự án số hóa di tích phường Định Công" → "...di sản Xã Như Quỳnh"; nội dung 2 đoạn đổi sang Như Quỳnh (vùng đất khoa bảng, hệ thống đình–chùa–đền–văn miếu).
5. Section "Di tích nổi bật" — heading: `Không gian đang mở tour VR360.` (giữ lưới `availableSites`).
6. Mảng `benefits` giữ nguyên (nội dung tổng quát, không nhắc Định Công). Kiểm tra: không còn chữ "Định Công" nào sót — search và thay hết.

- [ ] **Step 2: Verify build + dev**

Run: `npm run build`
Expected: PASS, không còn lỗi import (đã có đủ component + content).

Run: `npm run dev` rồi mở `http://localhost:3000/`
Expected: trang chủ hiển thị hero Như Quỳnh, lưới di tích nổi bật (Chùa Thái Lạc "Đang mở tour"), các di tích "Đang bổ sung tư liệu", không lỗi console nghiêm trọng.

- [ ] **Step 3: Commit**

```bash
git add src/app/page.tsx
git commit -m "feat: trang chủ Như Quỳnh"
```

---

### Task 11: Trang Danh mục di tích `/di-tich`

**Files:**
- Create: `phy-vr360/src/app/di-tich/page.tsx` (dựa trên pdc)

- [ ] **Step 1: Copy rồi sửa**

```bash
mkdir -p d:/Code/vr360/phy-vr360/src/app/di-tich
cp "d:/Code/vr360/pdc-vr360/app/di-tich/page.tsx" "d:/Code/vr360/phy-vr360/src/app/di-tich/page.tsx"
```

Sửa:
1. metadata: `Di tích | VR360 Như Quỳnh`; description Như Quỳnh.
2. kicker `Danh sách di tích và tour VR360` (giữ); h1 `Hệ thống di tích Xã Như Quỳnh`.
3. Đoạn mô tả → `Khám phá 15 di tích lịch sử — văn hóa của Xã Như Quỳnh: 9 di tích xếp hạng cấp quốc gia và 6 di tích cấp tỉnh.`
4. Ảnh hero phụ (`/images-tour/Chùa Liên Hoa/1 Trước Cổng.jpg`) → `activeTour.coverImage`.
5. Khối CTA cuối "phường Định Công" → "Xã Như Quỳnh".
6. `CulturalSpaceExplorer sites={heritageSites}` giữ nguyên.

- [ ] **Step 2: Verify build + dev**

Run: `npm run build`
Expected: PASS.

Run: mở `http://localhost:3000/di-tich`
Expected: 15 thẻ di tích; bộ lọc "Cấp xếp hạng" (Tất cả/Quốc gia/Tỉnh), "Trạng thái", "Loại địa điểm" hoạt động; tìm kiếm hoạt động; bộ đếm "Hiển thị X / 15".

- [ ] **Step 3: Commit**

```bash
git add src/app/di-tich/page.tsx
git commit -m "feat: trang danh mục 15 di tích Như Quỳnh có bộ lọc theo cấp"
```

---

### Task 12: Trang chi tiết di tích `/di-tich/[slug]`

**Files:**
- Create: `phy-vr360/src/app/di-tich/[slug]/page.tsx`

Trang này pdc chưa có (pdc dùng anchor) — tạo mới. Hiển thị 1 di tích từ `heritageSites`, dùng `SiteHeader/SiteFooter`, hỗ trợ `generateStaticParams`.

- [ ] **Step 1: Tạo file**

`phy-vr360/src/app/di-tich/[slug]/page.tsx`:

```tsx
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import { ArrowLeft, MapPin, PlayCircle, Tag } from "lucide-react";
import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";
import { heritageSites } from "@/app/content";

export function generateStaticParams() {
  return heritageSites.map((site) => ({ slug: site.id }));
}

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const { slug } = await params;
  const site = heritageSites.find((s) => s.id === slug);
  if (!site) return { title: "Không tìm thấy di tích | VR360 Như Quỳnh" };
  return {
    title: `${site.name} | VR360 Như Quỳnh`,
    description: site.shortDescription,
  };
}

export default async function HeritageDetailPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const site = heritageSites.find((s) => s.id === slug);
  if (!site) notFound();

  const paragraphs = site.longDescription ?? [site.shortDescription];

  return (
    <main className="public-page min-h-[100dvh] bg-[var(--background)] text-[var(--foreground)]">
      <SiteHeader />

      <section className="public-subpage-hero relative isolate overflow-hidden border-b border-[var(--surface-border)]">
        <Image src={site.image} alt={site.name} fill priority sizes="100vw" className="public-subpage-hero-image object-cover" />
        <div className="public-subpage-hero-content mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:py-22">
          <Link href="/di-tich" className="inline-flex items-center gap-2 text-sm font-bold text-[var(--tour-ink)] transition hover:text-[var(--primary)]">
            <ArrowLeft className="h-4 w-4" strokeWidth={1.8} />
            Tất cả di tích
          </Link>
          <div className="public-rise mt-5 max-w-4xl">
            <div className="flex flex-wrap gap-2">
              <span className="public-chip"><Tag className="h-4 w-4" strokeWidth={1.8} />{site.level}</span>
              <span className="public-chip">{site.type}</span>
              <span className="public-chip">{site.status}</span>
            </div>
            <h1 className="public-gradient-text public-heading-safe mt-4 text-[clamp(2.4rem,5.4vw,4.6rem)] font-extrabold">
              {site.name}
            </h1>
            {site.location ? (
              <p className="mt-4 inline-flex items-center gap-2 text-sm font-semibold text-[var(--foreground)]/82">
                <MapPin className="h-4 w-4 text-[var(--primary)]" strokeWidth={1.8} />
                {site.location}
              </p>
            ) : null}
          </div>
        </div>
      </section>

      <section className="mx-auto max-w-3xl px-4 py-14 sm:px-6 lg:py-20">
        <div className="grid gap-4 text-base leading-8 text-[var(--foreground)]/82">
          {paragraphs.map((text, i) => (
            <p key={i}>{text}</p>
          ))}
        </div>

        {site.isAvailable && site.vrUrl ? (
          <Link href={site.vrUrl} className="public-cta mt-8 w-fit">
            <PlayCircle className="h-4 w-4" strokeWidth={1.8} />
            Tham quan VR360
          </Link>
        ) : (
          <p className="public-panel mt-8 rounded-[8px] p-5 text-sm leading-6 text-[var(--muted-foreground)]">
            Tư liệu VR360 cho di tích này đang được bổ sung và sẽ sớm ra mắt.
          </p>
        )}
      </section>

      <SiteFooter />
      <BackToTopButton />
    </main>
  );
}
```

> Lưu ý Next.js 16: `params` là Promise (async). Nếu guide trong `node_modules/next/dist/docs/` cho biết khác, điều chỉnh theo guide.

- [ ] **Step 2: Verify build + dev**

Run: `npm run build`
Expected: PASS; build sinh được route động (15 trang qua `generateStaticParams`).

Run: mở `http://localhost:3000/di-tich/chua-thai-lac` và `http://localhost:3000/di-tich/dinh-at`
Expected: trang Thái Lạc có nút "Tham quan VR360"; trang Đình Ất hiển thị thông báo "đang bổ sung". Slug sai → trang 404.

- [ ] **Step 3: Commit**

```bash
git add "src/app/di-tich/[slug]/page.tsx"
git commit -m "feat: trang chi tiết di tích /di-tich/[slug]"
```

---

### Task 13: Trang Giới thiệu `/gioi-thieu`

**Files:**
- Create: `phy-vr360/src/app/gioi-thieu/page.tsx`

Tự viết (không copy pdc — nội dung khác hẳn), dùng layout chuẩn + class `public-*`.

- [ ] **Step 1: Tạo file**

`phy-vr360/src/app/gioi-thieu/page.tsx`:

```tsx
import type { Metadata } from "next";
import Image from "next/image";
import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";
import { activeTour, heritageSites } from "@/app/content";

export const metadata: Metadata = {
  title: "Giới thiệu | VR360 Như Quỳnh",
  description:
    "Giới thiệu về Xã Như Quỳnh, tỉnh Hưng Yên — vùng đất văn hiến và hệ thống di tích lịch sử — văn hóa được số hóa bằng công nghệ VR360.",
};

const nationalCount = heritageSites.filter((s) => s.level === "Cấp quốc gia").length;
const provincialCount = heritageSites.filter((s) => s.level === "Cấp tỉnh").length;

const stats = [
  { value: String(nationalCount), label: "di tích xếp hạng cấp quốc gia" },
  { value: String(provincialCount), label: "di tích cấp tỉnh" },
  { value: activeTour.sceneCount, label: "điểm 360° trong tour đầu tiên" },
];

const paragraphs = [
  "Xã Như Quỳnh, tỉnh Hưng Yên là vùng đất cổ giàu truyền thống văn hiến và khoa bảng, nơi lưu giữ một hệ thống di tích đình, chùa, đền, văn miếu phong phú trải dài nhiều thế kỷ.",
  "Dự án số hóa di sản được xây dựng nhằm bảo tồn tư liệu, quảng bá giá trị văn hóa địa phương và tạo điều kiện để người dân, học sinh, du khách tham quan trực tuyến các di tích tiêu biểu bằng công nghệ thực tế ảo VR360.",
  "Mỗi không gian được giới thiệu bằng hình ảnh thật, điểm tương tác, chú thích tiếng Việt và cấu trúc nội dung dễ theo dõi, hướng tới mục tiêu chuyển đổi số di sản giai đoạn 2026–2030.",
];

export default function AboutPage() {
  return (
    <main className="public-page min-h-[100dvh] bg-[var(--background)] text-[var(--foreground)]">
      <SiteHeader />

      <section className="public-subpage-hero public-subpage-hero--compact relative isolate overflow-hidden border-b border-[var(--surface-border)]">
        <Image src={activeTour.gateImage} alt="" fill priority sizes="100vw" className="public-subpage-hero-image object-cover" />
        <div className="public-subpage-hero-content mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:py-22">
          <div className="public-rise max-w-4xl">
            <p className="public-kicker">Giới thiệu</p>
            <h1 className="public-gradient-text public-heading-safe mt-4 text-[clamp(2.55rem,5.8vw,5rem)] font-extrabold">
              Di sản văn hóa Xã Như Quỳnh
            </h1>
          </div>
        </div>
      </section>

      <section className="mx-auto max-w-3xl px-4 py-14 sm:px-6 lg:py-20">
        <div className="grid gap-4 text-base leading-8 text-[var(--foreground)]/82">
          {paragraphs.map((text, i) => (
            <p key={i}>{text}</p>
          ))}
        </div>
      </section>

      <section className="border-y border-[var(--surface-border)] bg-[var(--surface-band)]">
        <div className="mx-auto grid max-w-7xl gap-4 px-4 py-14 sm:px-6 sm:grid-cols-3 lg:py-20">
          {stats.map((s) => (
            <div key={s.label} className="public-panel rounded-[8px] p-6 text-center">
              <p className="public-gradient-text text-4xl font-extrabold">{s.value}</p>
              <p className="mt-2 text-sm leading-6 text-[var(--muted-foreground)]">{s.label}</p>
            </div>
          ))}
        </div>
      </section>

      <SiteFooter />
      <BackToTopButton />
    </main>
  );
}
```

- [ ] **Step 2: Verify build + dev**

Run: `npm run build` → PASS. Mở `http://localhost:3000/gioi-thieu` → hiển thị nội dung + 3 thẻ thống kê (9 / 6 / 26).

- [ ] **Step 3: Commit**

```bash
git add src/app/gioi-thieu/page.tsx
git commit -m "feat: trang giới thiệu Xã Như Quỳnh"
```

---

### Task 14: Trang Liên hệ `/lien-he`

**Files:**
- Create: `phy-vr360/src/app/lien-he/page.tsx`

- [ ] **Step 1: Tạo file**

`phy-vr360/src/app/lien-he/page.tsx`:

```tsx
import type { Metadata } from "next";
import { MapPin, Mail, Phone } from "lucide-react";
import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";

export const metadata: Metadata = {
  title: "Liên hệ | VR360 Như Quỳnh",
  description: "Thông tin liên hệ và đầu mối cung cấp tư liệu cho dự án số hóa di sản Xã Như Quỳnh.",
};

const contacts = [
  { icon: MapPin, label: "Địa chỉ", value: "Xã Như Quỳnh, tỉnh Hưng Yên" },
  { icon: Phone, label: "Điện thoại", value: "Đang cập nhật" },
  { icon: Mail, label: "Email", value: "Đang cập nhật" },
];

export default function ContactPage() {
  return (
    <main className="public-page min-h-[100dvh] bg-[var(--background)] text-[var(--foreground)]">
      <SiteHeader />

      <section className="mx-auto max-w-3xl px-4 py-16 sm:px-6 lg:py-24">
        <p className="public-kicker">Liên hệ</p>
        <h1 className="public-gradient-text public-heading-safe mt-3 text-[clamp(2.2rem,5vw,3.6rem)] font-extrabold">
          Liên hệ & đóng góp tư liệu
        </h1>
        <p className="mt-5 text-base leading-8 text-[var(--foreground)]/82">
          Người dân, tổ chức có tư liệu, hình ảnh hoặc câu chuyện về các di tích của Xã Như Quỳnh
          vui lòng liên hệ để cùng hoàn thiện kho dữ liệu số di sản.
        </p>

        <div className="mt-10 grid gap-4">
          {contacts.map((c) => {
            const Icon = c.icon;
            return (
              <div key={c.label} className="public-panel flex items-center gap-4 rounded-[8px] p-5">
                <span className="grid h-11 w-11 shrink-0 place-items-center rounded-full bg-[rgb(200_164_106_/_0.18)]">
                  <Icon className="h-5 w-5 text-[var(--primary)]" strokeWidth={1.8} />
                </span>
                <span>
                  <span className="block text-sm font-bold text-[var(--muted-foreground)]">{c.label}</span>
                  <span className="block text-base font-bold text-[var(--tour-ink)]">{c.value}</span>
                </span>
              </div>
            );
          })}
        </div>
      </section>

      <SiteFooter />
      <BackToTopButton />
    </main>
  );
}
```

- [ ] **Step 2: Verify build + dev**

Run: `npm run build` → PASS. Mở `http://localhost:3000/lien-he` → hiển thị 3 thẻ liên hệ.

- [ ] **Step 3: Commit**

```bash
git add src/app/lien-he/page.tsx
git commit -m "feat: trang liên hệ"
```

---

## PHASE 3 — Tour VR360 Chùa Thái Lạc

### Task 15: Port engine VirtualTour & tổng quát hóa cho tour động

**Files:**
- Create: `phy-vr360/src/app/components/VirtualTour.tsx` (copy từ pdc + sửa)

Mục tiêu: engine chạy bằng config động, KHÔNG kèm dữ liệu Định Công/Liên Hoa (vì phy không có ảnh đó). Bước này đưa engine vào trạng thái biên dịch được với MỘT config rỗng tạm; Task 16 mới nạp dữ liệu Thái Lạc.

- [ ] **Step 1: Copy nguyên file engine**

```bash
cp "d:/Code/vr360/pdc-vr360/app/components/VirtualTour.tsx" "d:/Code/vr360/phy-vr360/src/app/components/VirtualTour.tsx"
```

- [ ] **Step 2: Tổng quát hóa kiểu định danh**

Trong file:
1. **`SceneId`** (dòng ~30, union các `"scene-1"|...`): đổi thành
```tsx
type SceneId = string;
```
2. **`TourConfig.id`** (dòng ~106) đổi từ union `"dinh-lang-dinh-cong" | "chua-lien-hoa"` thành `string`.
3. **Prop component** (dòng ~1121): đổi mặc định
```tsx
export default function VirtualTour({
  tourId = "chua-thai-lac",
}: {
  tourId?: string;
}) {
```

- [ ] **Step 3: Cắt bỏ dữ liệu Định Công & Liên Hoa**

Xóa các phần chỉ phục vụ 2 tour cũ (các hằng từ ~dòng 124 đến phần build config ~1014), gồm: `basePath`, `chuaBasePath`, `narrationBasePath`, `chuaNarrationBasePath`, `heroImage`, `welcomeBackgroundImage`, `dinhCardImage`, `shrineCardImage`, `backgroundAudio` (file nhạc Định Công), `dinhPracticalInfo`, `chuaPracticalInfo`, mảng `scenes` của Định Công, `continuousNarrationGroups`/`mapRoutes` của Định Công, `chuaScenes`/`chuaSceneNarration`/`chuaContinuousNarrationGroups`/`chuaMapRoutes`, `dinhTourConfig`, `chuaTourConfig`.

GIỮ LẠI: các type (`Hotspot`, `TourScene`, `TourConfig`, `MapRoute`, `WelcomeCardConfig`, `PracticalInfoItem`, `TourPracticalInfo`, `InfoMarker`...), hằng cấu hình camera (`PANORAMA_CAMERA_DISTANCE`, `DEFAULT_FOV`, ...), `hotspotIcon`/`infoModalBackground`/`welcomeTitleBackground`, hàm `createTourConfig`, toàn bộ logic render/Three.js, và cấu trúc `toursById`/`getTourConfig`.

Thay khối build config bằng import từ file config (tạo ở Task 16):

```tsx
import { chuaThaiLacTourConfig } from "@/app/tours/chua-thai-lac";

const toursById = {
  "chua-thai-lac": chuaThaiLacTourConfig,
} satisfies Record<string, TourConfig>;

const defaultTourConfig = chuaThaiLacTourConfig;

const getTourConfig = (tourId?: string) => toursById[tourId ?? ""] ?? defaultTourConfig;
```

Để `createTourConfig` và các type dùng được từ file config, **export** chúng:
```tsx
export type { TourConfig, TourScene, Hotspot, MapRoute, WelcomeCardConfig, TourPracticalInfo, PracticalInfoItem };
export { createTourConfig };
```
(và export các hằng path nếu config cần, ví dụ không cần — config tự định nghĩa path ảnh.)

> Vì khối Three.js render rất lớn (~3000 dòng) tham chiếu `tourConfig.*`, KHÔNG động vào phần đó. Chỉ thay phần dữ liệu + cách chọn config ở đầu/cuối file.

- [ ] **Step 4: Tạm thời chưa build được** — chuyển ngay sang Task 16 để có file config rồi mới verify chung. (Không commit lẻ bước này.)

---

### Task 16: Cấu hình tour Chùa Thái Lạc (26 cảnh)

**Files:**
- Create: `phy-vr360/src/app/tours/chua-thai-lac.ts`

- [ ] **Step 1: Khảo sát thứ tự cảnh từ ảnh**

Mở 26 ảnh trong `phy-vr360/public/images/Đình Thái Lạc/` để xác định tuyến đi hợp lý và hướng hotspot. Danh sách file (giữ đúng tên, có dấu/space — dùng `encodeURI`):
`4.jpg, Ao Cá Koi.jpg, Chính Diện Cổng Chùa.jpg, Chính Sảnh.jpg, Chính Điện 3.jpg, Chính.jpg, Chùa 1 Giữa.jpg, Chùa 1 Hữu.jpg, Chùa 1 Tả.jpg, Chùa 1 Trong - Giữa.jpg, Chùa 1 Trong Tả.jpg, Cổng Bên Trái.jpg, Cổng Hữu.jpg, Duong Tả Vòm.jpg, Giuawx.jpg, Gốc Ngoã Hữu.jpg, Gốc Ngoã Tả.jpg, Hữu 2.jpg, Hữu 3.jpg, La Hán Tả.jpg, Lối Vào Been Trái.jpg, Nhà Phật Hữu.jpg, Sau Chùa.jpg, Tượng Quan Âm.jpg, Đường Hữu.jpg, Đường Tả Qua Vòm.jpg`

- [ ] **Step 2: Viết file config**

Tạo `phy-vr360/src/app/tours/chua-thai-lac.ts`. Mẫu (điền đủ 26 cảnh; `mapPosition` rải đều trên sơ đồ 0–100; `hotspots` nối cảnh liền kề; yaw/pitch chỉnh ở Step 4):

```ts
import {
  createTourConfig,
  type TourConfig,
  type TourScene,
} from "@/app/components/VirtualTour";

const base = "/images/Đình Thái Lạc";
const p = (file: string) => encodeURI(`${base}/${file}`);

const scenes: TourScene[] = [
  {
    id: "scene-1",
    order: "01",
    title: "Chính diện cổng chùa",
    location: "Khu cổng",
    image: p("Chính Diện Cổng Chùa.jpg"),
    initialYaw: 0,
    mapPosition: { x: 50, y: 92 },
    hotspots: [
      { targetId: "scene-2", label: "Vào sân", yaw: 0, pitch: -12 },
    ],
  },
  // ... định nghĩa đủ scene-2 → scene-26 theo tuyến:
  // Cổng (Chính Diện Cổng Chùa, Cổng Hữu, Cổng Bên Trái, Lối Vào Been Trái)
  // → Đường/vòm (Đường Hữu, Đường Tả Qua Vòm, Duong Tả Vòm)
  // → Chính Sảnh → Chính → Chính Điện 3 → Giuawx → 4
  // → Ban thờ (Chùa 1 Tả/Giữa/Hữu, Chùa 1 Trong - Giữa, Chùa 1 Trong Tả, Hữu 2, Hữu 3)
  // → Tượng (Tượng Quan Âm, La Hán Tả, Nhà Phật Hữu)
  // → Ngoại cảnh (Ao Cá Koi, Gốc Ngoã Tả, Gốc Ngoã Hữu, Sau Chùa)
  // Mỗi scene có ÍT NHẤT 1 hotspot tiến + (nếu hợp lý) 1 hotspot lùi về cảnh trước.
];

export const chuaThaiLacTourConfig: TourConfig = createTourConfig({
  id: "chua-thai-lac",
  title: "Chùa Thái Lạc (Pháp Vân tự)",
  subtitle: "Di tích quốc gia — chạm khắc gỗ thời Trần",
  welcomeTitle: "Chùa Thái Lạc",
  welcomeAccent: "Pháp Vân tự",
  welcomeSubtitle:
    "Tham quan 360° ngôi chùa cổ thờ Pháp Vân, nổi tiếng với mảng chạm khắc gỗ thời Trần.",
  welcomeBackgroundImage: p("Chính Sảnh.jpg"),
  welcomeCards: [
    { image: p("Chính Diện Cổng Chùa.jpg"), label: "Cổng chùa", rotate: "-rotate-6", sceneId: "scene-1" },
    { image: p("Tượng Quan Âm.jpg"), label: "Đài Quan Âm", rotate: "rotate-5", sceneId: "scene-1" },
  ],
  backgroundAudio: "", // chưa có nhạc nền; engine xử lý chuỗi rỗng = không phát
  practicalInfo: {
    intro: "Thông tin phục vụ tham quan và lễ chùa tại Chùa Thái Lạc (Pháp Vân tự).",
    items: [
      { label: "Địa chỉ", value: "Xã Như Quỳnh, tỉnh Hưng Yên." },
      { label: "Người phụ trách", value: "Đang cập nhật." },
      { label: "Số điện thoại", value: "Đang cập nhật." },
      { label: "Giờ mở cửa", value: "Đón khách theo giờ hành chính và lịch sinh hoạt của nhà chùa." },
    ],
    notes: [
      "Giữ trang phục lịch sự, đi nhẹ nói khẽ, không tự ý di chuyển đồ thờ.",
      "Thông tin người phụ trách và liên hệ sẽ được cập nhật khi có dữ liệu xác thực.",
    ],
  },
  scenes,
  sceneNarration: new Map(), // chưa có file mp3 thuyết minh
  continuousNarrationGroups: [],
  mapRoutes: scenes
    .map((s, i) => (i < scenes.length - 1 ? { from: s.id, to: scenes[i + 1].id } : null))
    .filter((r): r is { from: string; to: string } => r !== null),
});
```

> ⚠️ Xác minh chữ ký `createTourConfig` thực tế trong VirtualTour.tsx (`Omit<TourConfig,"sceneById"|"locationGroups">`) và các field bắt buộc của `TourConfig`/`TourScene` đã export ở Task 15. Điền cho khớp 100% — KHÔNG để field thiếu. Nếu `sceneNarration` là `Map<SceneId,string[]>`, `new Map()` hợp lệ. Nếu `backgroundAudio` rỗng gây lỗi phát audio, kiểm tra logic audio trong engine và bọc điều kiện `if (backgroundAudio)` ở chỗ tạo `Audio()` (sửa tối thiểu trong VirtualTour.tsx, có ghi chú).

- [ ] **Step 3: Verify biên dịch (gộp Task 15+16)**

Run: `npx tsc --noEmit`
Expected: không lỗi type ở `VirtualTour.tsx` và `chua-thai-lac.ts`. Sửa cho đến khi sạch (các field config khớp type).

Run: `npm run build`
Expected: PASS.

- [ ] **Step 4: Commit (engine + config)**

```bash
git add src/app/components/VirtualTour.tsx src/app/tours/chua-thai-lac.ts
git commit -m "feat: port engine VirtualTour + cấu hình tour Chùa Thái Lạc"
```

---

### Task 17: Route tour `/tour/chua-thai-lac` + tinh chỉnh trải nghiệm

**Files:**
- Create: `phy-vr360/src/app/tour/chua-thai-lac/page.tsx`

- [ ] **Step 1: Tạo route**

`phy-vr360/src/app/tour/chua-thai-lac/page.tsx`:

```tsx
import type { Metadata } from "next";
import VirtualTour from "@/app/components/VirtualTour";

export const metadata: Metadata = {
  title: "VR360 Chùa Thái Lạc (Pháp Vân tự)",
  description:
    "Trải nghiệm tham quan thực tế ảo 360° Chùa Thái Lạc (Pháp Vân tự) — di tích quốc gia tại Xã Như Quỳnh, tỉnh Hưng Yên.",
};

export default function ChuaThaiLacTourPage() {
  return <VirtualTour tourId="chua-thai-lac" />;
}
```

- [ ] **Step 2: Verify dev (trải nghiệm tour)**

Run: `npm run dev` → mở `http://localhost:3000/tour/chua-thai-lac`
Kiểm tra:
- Màn hình welcome hiển thị tên Chùa Thái Lạc + thẻ vào cảnh.
- Vào tour: panorama 360° kéo/zoom được, ảnh tải đúng.
- Hotspot chuyển cảnh hoạt động, đi hết được 26 cảnh.
- Bảng "danh sách cảnh" (locationGroups), bản đồ tuyến, modal thông tin hiển thị.
- Không có lỗi 404 ảnh trong Network, không lỗi console nghiêm trọng.

- [ ] **Step 3: Tinh chỉnh hotspot/yaw**

Quan sát trực tiếp, chỉnh `initialYaw`, `yaw`/`pitch`/`rotation` của từng hotspot trong `chua-thai-lac.ts` cho mũi tên trỏ đúng hướng đi. Lặp lại đến khi tuyến tham quan mạch lạc.

- [ ] **Step 4: Verify build + commit**

Run: `npm run build` → PASS.

```bash
git add "src/app/tour/chua-thai-lac/page.tsx" src/app/tours/chua-thai-lac.ts
git commit -m "feat: route tour /tour/chua-thai-lac + tinh chỉnh hotspot"
```

---

## PHASE 4 — Hoàn thiện

### Task 18: Dọn scaffold mặc định & favicon/metadata

**Files:**
- Modify/verify: `phy-vr360/public/` (xóa `next.svg`, `vercel.svg`, `window.svg`, `globe.svg`, `file.svg` nếu không dùng)
- Verify: không còn tham chiếu nội dung Next.js mặc định

- [ ] **Step 1: Xóa asset scaffold không dùng**

```bash
cd d:/Code/vr360/phy-vr360/public
rm -f next.svg vercel.svg window.svg globe.svg file.svg
```

(Giữ `favicon.ico` trong `src/app/`.)

- [ ] **Step 2: Quét sót "Định Công" / "create next app"**

Run: `grep -rni "định công\|create next app\|dinhcong" d:/Code/vr360/phy-vr360/src`
Expected: KHÔNG còn kết quả. Nếu còn, sửa.

- [ ] **Step 3: Verify build**

Run: `npm run build` → PASS, không cảnh báo ảnh thiếu.

- [ ] **Step 4: Commit**

```bash
git add -A
git commit -m "chore: dọn scaffold mặc định, quét sạch nội dung Định Công"
```

---

### Task 19: Rà soát responsive, hiệu năng & nghiệm thu cuối

**Files:** không tạo mới — kiểm thử toàn site.

- [ ] **Step 1: Lint + build sạch**

Run: `npm run lint && npm run build`
Expected: cả hai PASS, không warning đáng kể.

- [ ] **Step 2: Kiểm tra responsive từng trang**

`npm run dev`, mở DevTools, kiểm tra ở 3 khổ (375px / 768px / 1280px) các trang: `/`, `/gioi-thieu`, `/di-tich`, `/di-tich/chua-thai-lac`, `/lien-he`, `/tour/chua-thai-lac`.
Kiểm: header/menu mobile mở-đóng đúng; lưới di tích không vỡ; không cuộn ngang; chữ không tràn.

- [ ] **Step 3: Kiểm điều hướng**

Click toàn bộ link điều hướng + nút "Tham quan VR360" + breadcrumb "Tất cả di tích" + "Back to top". Đảm bảo không link chết.

- [ ] **Step 4: Đối chiếu spec**

Mở `docs/superpowers/specs/2026-06-23-vr360-nhu-quynh-design.md`, xác nhận: 6 nhóm trang đủ; 15 di tích đúng phân cấp (9/6); tên "Chùa Thái Lạc (Pháp Vân tự)" đúng; tour hoạt động ngang `dinh-lang-dinh-cong`; pdc-vr360 không bị đụng tới.

- [ ] **Step 5: Commit (nếu có sửa)**

```bash
git add -A
git commit -m "fix: hoàn thiện responsive & nghiệm thu cổng di sản Như Quỳnh"
```

---

## Tự rà soát kế hoạch (đã thực hiện khi viết)

- **Phủ spec:** §2 cấu trúc trang → Task 10–14, 17; §3 danh mục 15 di tích → Task 5, 9, 11; §4 mô hình dữ liệu → Task 5; §5 tour Thái Lạc → Task 15–17; §6 design system → Task 2, 6–9; §1 dependencies/port → Task 1–4. Đủ.
- **Không placeholder:** mọi task có code thật hoặc lệnh copy file gốc cụ thể + danh sách sửa đổi rõ ràng. Phần điền 26 cảnh (Task 16) là công việc dữ liệu thực, kèm mẫu đầy đủ + thứ tự tuyến + yêu cầu khớp type.
- **Nhất quán type:** `HeritageSite` (Task 5) khớp field mà `CulturalSpaceExplorer` (Task 9), trang `[slug]` (Task 12), trang chủ/di-tich dùng. `TourConfig`/`createTourConfig` export ở Task 15 được Task 16 dùng đúng tên.
- **Điểm rủi ro đã gắn cờ:** API Next.js 16 (`params` Promise, đọc guide), chữ ký `createTourConfig` thực tế, xử lý `backgroundAudio` rỗng — đều có ghi chú kiểm chứng trong task tương ứng.
