feat: add video support for festival markers and implement video modal
This commit is contained in:
Binary file not shown.
@@ -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<HTMLVideoElement>(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 (
|
||||||
|
<div
|
||||||
|
className={`fixed inset-0 z-40 grid place-items-center bg-[rgb(10_8_6_/_0.74)] px-3 py-4 backdrop-blur-[4px] sm:px-5 sm:py-6 ${
|
||||||
|
isClosing
|
||||||
|
? "animate-[tourModalBackdropOut_240ms_ease-in_both]"
|
||||||
|
: "animate-[tourModalBackdrop_320ms_ease-out_both]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute inset-0 cursor-default"
|
||||||
|
onClick={onClose}
|
||||||
|
aria-label="Đóng video"
|
||||||
|
/>
|
||||||
|
<article className="relative w-[min(94vw,1100px)] max-h-[92dvh] overflow-hidden rounded-[14px] border border-[rgb(232_207_170_/_0.18)] bg-[rgb(20_17_14_/_0.94)] shadow-[0_38px_120px_rgb(0_0_0_/_0.6)] animate-[tourPanelIn_360ms_cubic-bezier(.2,.9,.2,1)_both]">
|
||||||
|
<div className="flex items-start justify-between gap-4 px-4 pb-2.5 pt-3.5 sm:px-5 sm:pt-4">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="text-[0.6rem] font-black uppercase tracking-[0.18em] text-[var(--tour-gold)] sm:text-[0.66rem]">
|
||||||
|
{marker.eyebrow}
|
||||||
|
</p>
|
||||||
|
<h2 className="font-display-vn mt-0.5 truncate text-xl font-bold text-[rgb(255,238,184)] sm:text-2xl">
|
||||||
|
{marker.title}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="grid h-8 w-8 shrink-0 place-items-center rounded-full border border-white/15 bg-white/10 text-white/90 backdrop-blur transition hover:bg-white/20 active:scale-95 sm:h-9 sm:w-9"
|
||||||
|
aria-label="Đóng video"
|
||||||
|
title="Đóng"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4 sm:h-[1.1rem] sm:w-[1.1rem]" strokeWidth={2.2} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative grid w-full place-items-center bg-black">
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
src={marker.video.src}
|
||||||
|
poster={marker.video.poster}
|
||||||
|
controls
|
||||||
|
autoPlay
|
||||||
|
playsInline
|
||||||
|
preload="auto"
|
||||||
|
className="aspect-video max-h-[64dvh] w-full object-contain"
|
||||||
|
>
|
||||||
|
Trình duyệt của bạn không hỗ trợ phát video.
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{marker.content && marker.content.length > 0 ? (
|
||||||
|
<div className="max-h-[20dvh] overflow-y-auto px-4 py-3.5 sm:px-5 sm:py-4">
|
||||||
|
<div className="space-y-2.5 font-display-vn text-[0.86rem] leading-6 text-white/72 sm:text-[0.92rem] sm:leading-7">
|
||||||
|
{marker.content.map((paragraph) => (
|
||||||
|
<p key={paragraph} className="text-justify">
|
||||||
|
{paragraph}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import * as THREE from "three";
|
|||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||||
import Lottie, { type LottieRefCurrentProps } from "lottie-react";
|
import Lottie, { type LottieRefCurrentProps } from "lottie-react";
|
||||||
import letterAnimation from "@/letter.json";
|
import letterAnimation from "@/letter.json";
|
||||||
|
import { TourVideoModal } from "@/app/components/TourVideoModal";
|
||||||
import { chuaThaiLacTour } from "@/app/tours/chua-thai-lac";
|
import { chuaThaiLacTour } from "@/app/tours/chua-thai-lac";
|
||||||
import { lqip } from "@/app/tours/chua-thai-lac.lqip";
|
import { lqip } from "@/app/tours/chua-thai-lac.lqip";
|
||||||
|
|
||||||
@@ -49,7 +50,9 @@ type InfoMarker = {
|
|||||||
pitch: number;
|
pitch: number;
|
||||||
rotation?: number;
|
rotation?: number;
|
||||||
content: string[];
|
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 = {
|
type TourScene = {
|
||||||
@@ -1213,6 +1216,22 @@ function TourExperience({
|
|||||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [activeInfoMarker, closeInfoMarker]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
const narrationAudio = narrationRef.current;
|
const narrationAudio = narrationRef.current;
|
||||||
|
|
||||||
@@ -2222,7 +2241,9 @@ function TourExperience({
|
|||||||
setActivePanel(null);
|
setActivePanel(null);
|
||||||
setIsInfoMarkerClosing(false);
|
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);
|
setActiveInfoMarker(marker);
|
||||||
setOpeningInfoMarkerId(null);
|
setOpeningInfoMarkerId(null);
|
||||||
return;
|
return;
|
||||||
@@ -2236,7 +2257,7 @@ function TourExperience({
|
|||||||
openingInfoTimerRef.current = null;
|
openingInfoTimerRef.current = null;
|
||||||
}, 520);
|
}, 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}
|
title={marker.title}
|
||||||
>
|
>
|
||||||
{/* TÌM ĐOẠN NÀY VÀ THAY TOÀN BỘ */}
|
{/* TÌM ĐOẠN NÀY VÀ THAY TOÀN BỘ */}
|
||||||
@@ -2255,6 +2276,34 @@ function TourExperience({
|
|||||||
rotation={marker.rotation ?? 0}
|
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.
|
||||||
|
<div
|
||||||
|
className={`relative aspect-video w-[5.5rem] overflow-hidden rounded-[10px] border backdrop-blur-md transition-all duration-300 sm:w-[6.75rem] ${isHovered
|
||||||
|
? "scale-[1.05] border-[rgba(255,234,176,1)] shadow-[0_0_0_3px_rgba(232,207,170,0.2),0_0_30px_rgba(255,210,115,0.5),0_16px_34px_rgba(0,0,0,0.44)]"
|
||||||
|
: "scale-100 border-[rgba(232,207,170,0.82)] shadow-[0_0_0_2px_rgba(232,207,170,0.14),0_0_18px_rgba(232,207,170,0.3),0_10px_24px_rgba(0,0,0,0.42)]"} ${isRead ? "opacity-95" : "opacity-100"}`}
|
||||||
|
>
|
||||||
|
{!isRead ? (
|
||||||
|
<span className="tour-temple-icon-pulse absolute inset-[-0.55rem] -z-10 rounded-[16px]" />
|
||||||
|
) : null}
|
||||||
|
{/* Nền giả lập khung phim + ánh sáng nhẹ phía trên cho ra dáng màn hình */}
|
||||||
|
<span className="absolute inset-0 bg-[linear-gradient(135deg,rgba(40,20,16,0.96),rgba(120,42,33,0.9)_52%,rgba(18,12,10,0.97))]" />
|
||||||
|
<span className="absolute inset-x-0 top-0 h-1/2 bg-[linear-gradient(180deg,rgba(255,246,214,0.16),transparent)]" />
|
||||||
|
{/* Nút play ở giữa khung */}
|
||||||
|
<span className="absolute inset-0 grid place-items-center">
|
||||||
|
<span
|
||||||
|
className={`grid h-7 w-7 place-items-center rounded-full border bg-[rgba(8,6,5,0.46)] backdrop-blur-sm transition-all duration-300 sm:h-8 sm:w-8 ${isHovered
|
||||||
|
? "scale-110 border-[rgba(255,234,176,1)] bg-[rgba(8,6,5,0.58)]"
|
||||||
|
: "border-white/55"}`}
|
||||||
|
>
|
||||||
|
<Play
|
||||||
|
className={`h-3.5 w-3.5 translate-x-[1px] fill-current transition-colors duration-300 sm:h-4 sm:w-4 ${isHovered ? "text-white" : "text-[rgb(255,238,184)]"}`}
|
||||||
|
strokeWidth={1.4}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
@@ -2299,7 +2348,20 @@ function TourExperience({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeInfoMarker ? (
|
{activeInfoMarker?.video ? (
|
||||||
|
<TourVideoModal
|
||||||
|
marker={{
|
||||||
|
title: activeInfoMarker.title,
|
||||||
|
eyebrow: activeInfoMarker.eyebrow,
|
||||||
|
content: activeInfoMarker.content,
|
||||||
|
video: activeInfoMarker.video,
|
||||||
|
}}
|
||||||
|
isClosing={isInfoMarkerClosing}
|
||||||
|
onClose={closeInfoMarker}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{activeInfoMarker && !activeInfoMarker.video ? (
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-0 z-40 grid place-items-center bg-[rgb(22_18_15_/_0.44)] px-3 py-4 backdrop-blur-[3px] sm:px-5 sm:py-6 ${isInfoMarkerClosing
|
className={`fixed inset-0 z-40 grid place-items-center bg-[rgb(22_18_15_/_0.44)] px-3 py-4 backdrop-blur-[3px] sm:px-5 sm:py-6 ${isInfoMarkerClosing
|
||||||
? "animate-[tourModalBackdropOut_240ms_ease-in_both]"
|
? "animate-[tourModalBackdropOut_240ms_ease-in_both]"
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { Play } from "lucide-react";
|
||||||
|
|
||||||
|
// Trình phát video lễ hội: hiện poster + nút phát tuỳ biến cho tới khi người dùng bấm,
|
||||||
|
// sau đó mới nạp video và bật điều khiển gốc của trình duyệt.
|
||||||
|
// preload="none" để không tải file video nặng khi chưa cần — tốt cho mobile & băng thông.
|
||||||
|
export function FestivalVideo({
|
||||||
|
src,
|
||||||
|
poster,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
src: string;
|
||||||
|
poster: string;
|
||||||
|
title: string;
|
||||||
|
}) {
|
||||||
|
const videoRef = useRef<HTMLVideoElement>(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 (
|
||||||
|
<div className="group relative overflow-hidden rounded-[18px] border border-[var(--surface-border)] bg-black shadow-[0_30px_86px_rgb(37_75_54_/_0.18)]">
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
src={src}
|
||||||
|
poster={poster}
|
||||||
|
controls={started}
|
||||||
|
preload="none"
|
||||||
|
playsInline
|
||||||
|
className="aspect-video w-full object-cover"
|
||||||
|
>
|
||||||
|
Trình duyệt của bạn không hỗ trợ phát video.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
{!started && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handlePlay}
|
||||||
|
aria-label={`Phát video: ${title}`}
|
||||||
|
className="absolute inset-0 flex flex-col items-center justify-center gap-4 bg-[linear-gradient(180deg,rgb(8_31_23_/_0.26),rgb(8_31_23_/_0.6))] transition"
|
||||||
|
>
|
||||||
|
<span className="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-full border border-white/40 bg-[rgb(8_31_23_/_0.42)] text-white backdrop-blur-md transition duration-300 group-hover:scale-105 group-hover:border-[var(--tour-gold-light)] group-hover:bg-[rgb(8_31_23_/_0.58)]">
|
||||||
|
<Play className="h-7 w-7 translate-x-[2px] fill-current" strokeWidth={1.5} />
|
||||||
|
</span>
|
||||||
|
<span className="rounded-full border border-white/20 bg-[rgb(8_31_23_/_0.46)] px-4 py-1.5 text-sm font-bold text-white backdrop-blur-md">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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ổ" },
|
{ file: "25.jpg", title: "Thượng điện", note: "Khám thờ gỗ và hệ cột cổ" },
|
||||||
] as const;
|
] 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 = [
|
export const navigation = [
|
||||||
{ href: "/", label: "Trang chủ" },
|
{ href: "/", label: "Trang chủ" },
|
||||||
{ href: "/gioi-thieu", label: "Giới thiệu" },
|
{ href: "/gioi-thieu", label: "Giới thiệu" },
|
||||||
|
|||||||
+30
-1
@@ -3,12 +3,13 @@ import Image from "next/image";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowRight, Box, Globe2, Landmark, PlayCircle, Rotate3d, School, Sparkles } from "lucide-react";
|
import { ArrowRight, Box, Globe2, Landmark, PlayCircle, Rotate3d, School, Sparkles } from "lucide-react";
|
||||||
import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
|
import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
|
||||||
|
import { FestivalVideo } from "@/app/components/landing/FestivalVideo";
|
||||||
import { HeritageMarquee } from "@/app/components/landing/HeritageMarquee";
|
import { HeritageMarquee } from "@/app/components/landing/HeritageMarquee";
|
||||||
import { SiteFooter } from "@/app/components/landing/SiteFooter";
|
import { SiteFooter } from "@/app/components/landing/SiteFooter";
|
||||||
import { SiteHeader } from "@/app/components/landing/SiteHeader";
|
import { SiteHeader } from "@/app/components/landing/SiteHeader";
|
||||||
import { Typewriter } from "@/app/components/landing/Typewriter";
|
import { Typewriter } from "@/app/components/landing/Typewriter";
|
||||||
import { HeroShaderBackground } from "@/app/components/ui/hero-shader-background";
|
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 = {
|
export const metadata: Metadata = {
|
||||||
title: "Trang chủ | VR360 Như Quỳnh",
|
title: "Trang chủ | VR360 Như Quỳnh",
|
||||||
@@ -133,6 +134,34 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* 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 */}
|
||||||
|
<section className="border-y border-[var(--surface-border)] bg-[var(--surface-band)]">
|
||||||
|
<div className="mx-auto grid max-w-7xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-[0.82fr_1.18fr] lg:items-center lg:py-20">
|
||||||
|
<div className="public-split-reveal min-w-0 rounded-[6px]">
|
||||||
|
<p className="public-kicker">{festivalVideo.kicker}</p>
|
||||||
|
<h2 className="public-display public-heading-safe mt-3 text-[2rem] font-bold text-[var(--tour-ink)] sm:text-4xl">
|
||||||
|
{festivalVideo.heading}
|
||||||
|
</h2>
|
||||||
|
<div className="mt-4 grid gap-3 text-sm leading-7 text-[var(--muted-foreground)] sm:text-base">
|
||||||
|
{festivalVideo.description.map((para) => (
|
||||||
|
<p key={para}>{para}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href={festivalVideo.ctaHref}
|
||||||
|
className="mt-6 inline-flex items-center gap-2 text-sm font-extrabold text-[var(--tour-ink)] transition hover:text-[var(--primary)]"
|
||||||
|
>
|
||||||
|
{festivalVideo.ctaLabel}
|
||||||
|
<ArrowRight className="h-4 w-4" strokeWidth={1.8} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="public-stagger-item min-w-0">
|
||||||
|
<FestivalVideo src={festivalVideo.src} poster={festivalVideo.poster} title={festivalVideo.title} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="mx-auto grid max-w-7xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-[0.8fr_1.2fr] lg:items-center lg:py-20">
|
<section className="mx-auto grid max-w-7xl gap-10 px-4 py-14 sm:px-6 lg:grid-cols-[0.8fr_1.2fr] lg:items-center lg:py-20">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="public-kicker">Giới thiệu nhanh</p>
|
<p className="public-kicker">Giới thiệu nhanh</p>
|
||||||
|
|||||||
@@ -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.",
|
"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
|
// 8 — ảnh 09: sân gạch, tam quan bên trái, đài Quan Âm bên phải
|
||||||
|
|||||||
Reference in New Issue
Block a user