# 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 (
{site.isAvailable && site.vrUrl ? (
Tham quan VR360
) : (
Tư liệu VR360 cho di tích này đang được bổ sung và sẽ sớm ra mắt.
)}
);
}
```
> 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 (
Giới thiệu
Di sản văn hóa Xã Như Quỳnh
{paragraphs.map((text, i) => (
{text}
))}
{stats.map((s) => (
{s.value}
{s.label}
))}
);
}
```
- [ ] **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 (
Liên hệ
Liên hệ & đóng góp tư liệu
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.
);
}
```
- [ ] **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;
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`) 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`, `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 ;
}
```
- [ ] **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.