diff --git a/public/video/le-hoi-cau-mua-lac-hong-van-lam.mp4 b/public/video/le-hoi-cau-mua-lac-hong-van-lam.mp4 new file mode 100644 index 0000000..4e47a0c Binary files /dev/null and b/public/video/le-hoi-cau-mua-lac-hong-van-lam.mp4 differ diff --git a/src/app/components/TourVideoModal.tsx b/src/app/components/TourVideoModal.tsx new file mode 100644 index 0000000..2d52b9d --- /dev/null +++ b/src/app/components/TourVideoModal.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { X } from "lucide-react"; + +export type TourVideoMarker = { + title: string; + eyebrow: string; + content?: string[]; + video: { src: string; poster?: string }; +}; + +// Modal phát video cho marker trong tour VR360. Mở rộng (16:9), tự phát khi mở +// (trong chuỗi cử chỉ người dùng); nếu trình duyệt chặn autoplay có tiếng thì +// controls vẫn hiển thị để người dùng tự phát. Backdrop + card dùng lại animation +// toàn cục của VirtualTour (tourModalBackdrop / tourPanelIn). +export function TourVideoModal({ + marker, + isClosing, + onClose, +}: { + marker: TourVideoMarker; + isClosing: boolean; + onClose: () => void; +}) { + const videoRef = useRef(null); + + useEffect(() => { + const video = videoRef.current; + if (!video) { + return; + } + + void video.play().catch(() => { + // Autoplay có tiếng bị chặn — controls đã bật để người dùng tự phát. + }); + + return () => { + // Dừng ngay khi đóng để không còn tiếng vọng trong lúc backdrop mờ dần. + video.pause(); + }; + }, []); + + return ( +
+ +
+ +
+ +
+ + {marker.content && marker.content.length > 0 ? ( +
+
+ {marker.content.map((paragraph) => ( +

+ {paragraph} +

+ ))} +
+
+ ) : null} + + + ); +} diff --git a/src/app/components/VirtualTour.tsx b/src/app/components/VirtualTour.tsx index 8efe3a7..52aa0e3 100644 --- a/src/app/components/VirtualTour.tsx +++ b/src/app/components/VirtualTour.tsx @@ -26,6 +26,7 @@ import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import Lottie, { type LottieRefCurrentProps } from "lottie-react"; import letterAnimation from "@/letter.json"; +import { TourVideoModal } from "@/app/components/TourVideoModal"; import { chuaThaiLacTour } from "@/app/tours/chua-thai-lac"; import { lqip } from "@/app/tours/chua-thai-lac.lqip"; @@ -49,7 +50,9 @@ type InfoMarker = { pitch: number; rotation?: number; content: string[]; - iconType?: "envelope" | "landmark" | "info"; + iconType?: "envelope" | "landmark" | "info" | "video"; + // Khi có `video`, marker mở modal phát video thay vì popup chữ (dùng iconType "video"). + video?: { src: string; poster?: string }; }; type TourScene = { @@ -1213,6 +1216,22 @@ function TourExperience({ return () => window.removeEventListener("keydown", handleKeyDown); }, [activeInfoMarker, closeInfoMarker]); + // Marker video đang mở → dừng thuyết minh và hạ nhạc nền để không chồng tiếng với + // video; khi đóng thì khôi phục âm lượng nền. Tái dùng tín hiệu duck sẵn có + // (onNarrationStateChange) nên không cần thêm prop điều khiển audio. + const isVideoMarkerOpen = Boolean(activeInfoMarker?.video) && !isInfoMarkerClosing; + useEffect(() => { + if (!isVideoMarkerOpen) { + return; + } + + stopNarration(); + onNarrationStateChange?.(true); + return () => { + onNarrationStateChange?.(false); + }; + }, [isVideoMarkerOpen, stopNarration, onNarrationStateChange]); + useEffect(() => { const narrationAudio = narrationRef.current; @@ -2222,7 +2241,9 @@ function TourExperience({ setActivePanel(null); setIsInfoMarkerClosing(false); - if (isRead) { + // Marker video mở modal phát ngay, không qua hiệu ứng "mở thư" 520ms. + if (marker.video || isRead) { + setReadMarkers((prev) => new Set(prev).add(markerKey)); setActiveInfoMarker(marker); setOpeningInfoMarkerId(null); return; @@ -2236,7 +2257,7 @@ function TourExperience({ openingInfoTimerRef.current = null; }, 520); }} - aria-label={`Mở thông tin ${marker.title}`} + aria-label={marker.video ? `Phát video: ${marker.title}` : `Mở thông tin ${marker.title}`} title={marker.title} > {/* TÌM ĐOẠN NÀY VÀ THAY TOÀN BỘ */} @@ -2255,6 +2276,34 @@ function TourExperience({ rotation={marker.rotation ?? 0} /> + ) : marker.iconType === "video" ? ( + // Marker dạng KHUNG VIDEO thu nhỏ (tỉ lệ 16:9) — giống màn hình phát video + // mini, có nút play ở giữa; bấm để mở modal phát to. +
+ {!isRead ? ( + + ) : null} + {/* Nền giả lập khung phim + ánh sáng nhẹ phía trên cho ra dáng màn hình */} + + + {/* Nút play ở giữa khung */} + + + + + +
) : (
- {activeInfoMarker ? ( + {activeInfoMarker?.video ? ( + + ) : null} + + {activeInfoMarker && !activeInfoMarker.video ? (
(null); + const [started, setStarted] = useState(false); + + function handlePlay() { + const video = videoRef.current; + if (!video) return; + setStarted(true); + void video.play().catch(() => { + // Một số trình duyệt chặn phát tự động — controls đã bật để người dùng tự phát. + }); + } + + return ( +
+ + + {!started && ( + + )} +
+ ); +} diff --git a/src/app/content.ts b/src/app/content.ts index 97b6088..d9a6e66 100644 --- a/src/app/content.ts +++ b/src/app/content.ts @@ -42,6 +42,23 @@ export const featuredSpaces = [ { file: "25.jpg", title: "Thượng điện", note: "Khám thờ gỗ và hệ cột cổ" }, ] as const; +// Video lễ hội truyền thống — tư liệu động bổ sung cho không gian di sản tĩnh. +// Gắn với tín ngưỡng Tứ Pháp (cầu mưa) của cư dân nông nghiệp Bắc Bộ, cùng hệ tín +// ngưỡng thờ Pháp Vân hiện diện ngay tại Chùa Thái Lạc của vùng Như Quỳnh. +export const festivalVideo = { + kicker: "Lễ hội & văn hóa", + title: "Lễ hội cầu mưa Lạc Hồng – Văn Lâm", + src: "/video/le-hoi-cau-mua-lac-hong-van-lam.mp4", + poster: img("10.jpg"), // Đài lộ thiên giữa sân chùa — không gian nghi lễ ngoài trời + heading: "Lễ hội cầu mưa — nhịp sống tín ngưỡng Tứ Pháp", + description: [ + "Lễ hội cầu mưa ở Lạc Hồng (Văn Lâm, Hưng Yên) là sinh hoạt tín ngưỡng lâu đời của cư dân nông nghiệp đồng bằng Bắc Bộ, gắn với hệ Tứ Pháp — Pháp Vân, Pháp Vũ, Pháp Lôi, Pháp Điện — cầu cho mưa thuận gió hòa, mùa màng tươi tốt.", + "Cùng dòng tín ngưỡng ấy, Chùa Thái Lạc (Pháp Vân tự) của vùng Như Quỳnh thờ vị thần Mây Pháp Vân, để lại dấu ấn đậm nét trên những mảng chạm khắc gỗ thời Trần còn lưu giữ đến nay.", + ], + ctaLabel: "Khám phá VR360 Chùa Thái Lạc", + ctaHref: "/tour/chua-thai-lac", +} as const; + export const navigation = [ { href: "/", label: "Trang chủ" }, { href: "/gioi-thieu", label: "Giới thiệu" }, diff --git a/src/app/page.tsx b/src/app/page.tsx index befbf2c..ecc8c02 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,12 +3,13 @@ import Image from "next/image"; import Link from "next/link"; import { ArrowRight, Box, Globe2, Landmark, PlayCircle, Rotate3d, School, Sparkles } from "lucide-react"; import { BackToTopButton } from "@/app/components/landing/BackToTopButton"; +import { FestivalVideo } from "@/app/components/landing/FestivalVideo"; import { HeritageMarquee } from "@/app/components/landing/HeritageMarquee"; import { SiteFooter } from "@/app/components/landing/SiteFooter"; import { SiteHeader } from "@/app/components/landing/SiteHeader"; import { Typewriter } from "@/app/components/landing/Typewriter"; import { HeroShaderBackground } from "@/app/components/ui/hero-shader-background"; -import { activeTour, featuredSpaces, heritageSites, img } from "@/app/content"; +import { activeTour, featuredSpaces, festivalVideo, heritageSites, img } from "@/app/content"; export const metadata: Metadata = { title: "Trang chủ | VR360 Như Quỳnh", @@ -133,6 +134,34 @@ export default function Home() {
+ {/* Lễ hội & văn hóa — tư liệu video động, gắn với tín ngưỡng Tứ Pháp của vùng */} +
+
+
+

{festivalVideo.kicker}

+

+ {festivalVideo.heading} +

+
+ {festivalVideo.description.map((para) => ( +

{para}

+ ))} +
+ + {festivalVideo.ctaLabel} + + +
+ +
+ +
+
+
+

Giới thiệu nhanh

diff --git a/src/app/tours/chua-thai-lac.ts b/src/app/tours/chua-thai-lac.ts index 698c90c..5a30755 100644 --- a/src/app/tours/chua-thai-lac.ts +++ b/src/app/tours/chua-thai-lac.ts @@ -151,6 +151,23 @@ const seeds: SceneSeed[] = [ "Bao quanh sân là hệ thống cây cảnh và các cây duối cổ thụ được cắt tỉa, uốn thế dạng tầng tán mâm xôi bề thế, đặt trong các bồn đá tròn. Phía góc sân hướng ra bên ngoài là khoảng không gian trống có treo cờ Phật giáo, kết hợp với các dãy chậu cây cảnh xếp dọc tường bao. Phía đối diện là một lễ đài lớn ngoài trời có dựng bức bạt tạo hình đóa hoa sen tôn trí hình ảnh Đức Phật, hoàn thiện kết cấu cảnh quan xung quanh sân chính.", ], }, + // Marker VIDEO đặt ngay đỉnh hương đá giữa sân — bấm để mở modal phát phim lễ hội. + // yaw/pitch tinh chỉnh cho trùng vị trí lư hương: cùng hướng marker chữ (yaw 90) + // nhưng hạ thấp xuống mặt sân (pitch âm) để hai marker không chồng nhau. + { + id: "video-le-hoi-cau-mua", + title: "Lễ hội cầu mưa", + eyebrow: EYEBROW, + yaw: 119, + pitch: 2, + iconType: "video", + video: { + src: "/video/le-hoi-cau-mua-lac-hong-van-lam.mp4", + }, + content: [ + "Lễ hội cầu mưa gắn với tín ngưỡng Tứ Pháp của cư dân nông nghiệp đồng bằng Bắc Bộ — cầu cho mưa thuận gió hòa, mùa màng tươi tốt. Tại vùng Như Quỳnh, dòng tín ngưỡng ấy hiện diện ngay ở Chùa Thái Lạc (Pháp Vân tự), nơi thờ vị thần Mây Pháp Vân.", + ], + }, ], }, // 8 — ảnh 09: sân gạch, tam quan bên trái, đài Quan Âm bên phải