feat: add FestivalGallery component for showcasing festival moments with responsive grid layout

This commit is contained in:
tuyenio
2026-06-26 15:33:32 +07:00
parent 9519e395e0
commit 95a0836c11
27 changed files with 157 additions and 13 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

+2 -2
View File
@@ -5,7 +5,7 @@ import AntiquityShowcaseClient from "./AntiquityShowcaseClient";
import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";
import { activeTour, antiquities } from "@/app/content";
import { antiquities, subpageHero } from "@/app/content";
export const metadata: Metadata = {
title: "Cổ vật 3D | VR360 Như Quỳnh",
@@ -25,7 +25,7 @@ export default function AntiquityPage() {
<SiteHeader />
<section className="public-subpage-hero public-subpage-hero--compact relative isolate overflow-hidden border-b border-[var(--surface-border)]">
<Image src={activeTour.shrineImage} alt="" fill priority sizes="100vw" className="public-subpage-hero-image public-parallax-bg object-cover" />
<Image src={subpageHero.coVat} alt="" fill priority sizes="100vw" className="public-subpage-hero-image public-parallax-bg 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 public-hero-rule max-w-4xl">
<p className="public-kicker">Phòng trưng bày số</p>
@@ -0,0 +1,48 @@
import Image from "next/image";
import { festivalGallery } from "@/app/content";
// Gallery "Khoảnh khắc lễ hội" — bố cục bento: dưới lg là lưới 2 cột đều, từ lg dùng
// grid-auto-flow dense với vài ô lớn/cao để tạo nhịp điệu biên tập, tránh kiểu 3 thẻ đều.
export function FestivalGallery() {
return (
<section className="mx-auto max-w-7xl px-4 py-14 sm:px-6 lg:py-20">
<div className="public-split-reveal max-w-3xl rounded-[6px]">
<p className="public-kicker">Khoảnh khắc lễ hội</p>
<h2 className="public-display public-heading-safe mt-3 text-[2rem] font-bold text-[var(--tour-ink)] sm:text-4xl">
Sắc màu hội làng bên ngôi chùa cổ
</h2>
<p className="mt-3 max-w-[64ch] text-sm leading-6 text-[var(--muted-foreground)] sm:text-base">
Những khoảnh khắc thật của lễ hội truyền thống tại Chùa Pháp Vân (Thái Lạc) đám rước, múa rồng,
nghi thức tế lễ đêm cầu an bên ngôi chùa cổ.
</p>
</div>
<div className="mt-9 grid auto-rows-[9.5rem] grid-cols-2 gap-3 sm:auto-rows-[12rem] sm:gap-4 lg:auto-rows-[12.5rem] lg:grid-cols-4 lg:[grid-auto-flow:dense]">
{festivalGallery.map((photo) => (
<figure
key={photo.src}
className={`public-stagger-item group relative overflow-hidden rounded-[14px] ring-1 ring-[var(--surface-border)] shadow-[0_18px_46px_rgb(37_75_54_/_0.14)] ${photo.span ?? ""}`}
>
<Image
src={photo.src}
alt={photo.alt}
fill
sizes="(max-width: 1024px) 50vw, 25vw"
className="object-cover transition-transform duration-[900ms] ease-[cubic-bezier(.2,.9,.2,1)] group-hover:scale-[1.06]"
/>
<div
aria-hidden
className="absolute inset-0 bg-[linear-gradient(180deg,transparent_36%,rgb(8_31_23_/_0.82))] transition-opacity duration-500 group-hover:opacity-100"
/>
<figcaption className="absolute inset-x-0 bottom-0 p-4 sm:p-5">
<p className="font-display-vn text-base font-extrabold leading-tight text-white drop-shadow-sm sm:text-lg">
{photo.title}
</p>
<p className="mt-1 text-[0.78rem] leading-5 text-white/82 sm:text-sm">{photo.note}</p>
</figcaption>
</figure>
))}
</div>
</section>
);
}
+94 -2
View File
@@ -21,7 +21,10 @@ 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("01.jpg"), // Chính diện cổng chùa
// Ảnh nền hero (CSS background) — toàn cảnh lễ hội múa rồng, ảnh thật của di tích.
heroImage: "/images/le-hoi/toan-canh-le-hoi.jpg",
// Ảnh lớn bên phải hero — nghi lễ rước kiệu (qua next/image).
heroFeatureImage: "/images/le-hoi/ruoc-kieu.jpg",
coverImage: img("08.jpg"), // Chính sảnh
gateImage: img("01.jpg"), // Chính diện cổng chùa
shrineImage: img("11.jpg"), // Chính điện
@@ -49,7 +52,7 @@ 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
poster: "/images/le-hoi/san-chua-tu-tren-cao.jpg", // Ảnh thật sân chùa ngày hộ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.",
@@ -59,6 +62,95 @@ export const festivalVideo = {
ctaHref: "/tour/chua-thai-lac",
} as const;
// Ảnh lễ hội thật của Chùa Pháp Vân (Thái Lạc) — Quang Trung, Lạc Hồng, Văn Lâm, Hưng Yên.
// Đặt tại public/images/le-hoi/ với tên ASCII đã tối ưu (resize + nén) để an toàn khi triển khai.
const leHoi = (file: string) => `/images/le-hoi/${file}`;
export type FestivalPhoto = {
src: string;
title: string;
note: string;
alt: string;
// Lớp span Tailwind cho bố cục bento ở breakpoint lg (dưới lg là lưới 2 cột đều).
span?: string;
};
// Bộ ảnh "Khoảnh khắc lễ hội" — bố cục bento ở trang chủ. Ô đầu (đêm cầu an) làm ô chủ đạo.
export const festivalGallery: FestivalPhoto[] = [
{
src: leHoi("le-cau-an-ban-dem.jpg"),
title: "Lễ cầu an",
note: "Phật tử dâng đèn hoa sen trong đêm hội",
alt: "Phật tử áo tràng chắp tay bên đèn hoa sen trong lễ cầu an ban đêm tại Chùa Pháp Vân",
span: "lg:col-span-2 lg:row-span-2",
},
{
src: leHoi("toan-canh-le-hoi.jpg"),
title: "Toàn cảnh đám rước",
note: "Múa rồng và đoàn rước rợp sắc cờ",
alt: "Toàn cảnh đám rước lễ hội với hai đoàn múa rồng vàng giữa hàng cây",
span: "lg:col-span-2",
},
{
src: leHoi("ruoc-kieu.jpg"),
title: "Rước kiệu thánh",
note: "Kiệu sơn son thếp vàng giữa sân lễ",
alt: "Kiệu thánh sơn son thếp vàng được trai làng khiêng trong đám rước",
},
{
src: leHoi("doan-khieng-kieu.jpg"),
title: "Trai làng rước kiệu",
note: "Khí thế đoàn rước qua đường làng",
alt: "Đoàn trai làng cởi trần thắt đai đỏ vàng khiêng kiệu thánh qua đường làng",
span: "lg:row-span-2",
},
{
src: leHoi("mua-rong-cong-chua.jpg"),
title: "Múa rồng trước Tam quan",
note: "Sắc áo dài rực rỡ hai bên lối vào",
alt: "Màn múa rồng trước Tam quan chùa với hàng người mặc áo dài cầm quạt hai bên",
},
{
src: leHoi("nghi-thuc-san-chua.jpg"),
title: "Nghi thức tế lễ",
note: "Đội tế xếp hàng giữa sân chùa",
alt: "Đội tế xếp hàng làm nghi thức giữa sân gạch chùa rợp cờ Phật giáo nhìn từ trên cao",
span: "lg:col-span-2",
},
{
src: leHoi("kieu-long-vang.jpg"),
title: "Kiệu dưới tán lọng",
note: "Lọng vàng che kiệu trong đám rước",
alt: "Kiệu thánh dưới tán lọng vàng giữa dòng người trẩy hội",
},
{
src: leHoi("san-chua-tu-tren-cao.jpg"),
title: "Sân chùa ngày hội",
note: "Rợp lọng vàng nhìn từ trên cao",
alt: "Sân chùa ngày hội rợp lọng vàng và dòng người nhìn từ trên cao",
},
{
src: leHoi("doan-ruoc-bo-lao.jpg"),
title: "Đoàn rước bô lão",
note: "Các cụ trong trang phục tế truyền thống",
alt: "Đoàn các cụ bô lão mặc áo tế truyền thống cầm gậy đi trong đám rước",
},
{
src: leHoi("tru-tri-le-hoi.jpg"),
title: "Sư trụ trì chủ lễ",
note: "Chủ lễ giữa đoàn rước long trọng",
alt: "Sư trụ trì khoác áo cà sa vàng đi giữa hai hàng người cầm lọng và cờ lễ",
},
];
// Ảnh phần đầu (hero) của các trang con — dùng ảnh lễ hội thật thay cho ảnh panorama bị méo.
export const subpageHero = {
gioiThieu: leHoi("mua-rong-cong-chua.jpg"),
diTich: leHoi("nghi-thuc-san-chua.jpg"),
diTichFeature: leHoi("kieu-long-vang.jpg"),
coVat: leHoi("tru-tri-le-hoi.jpg"),
} as const;
export const navigation = [
{ href: "/", label: "Trang chủ" },
{ href: "/gioi-thieu", label: "Giới thiệu" },
+4 -4
View File
@@ -6,7 +6,7 @@ import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
import { CulturalSpaceExplorer } from "@/app/components/landing/CulturalSpaceExplorer";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";
import { activeTour, heritageSites } from "@/app/content";
import { heritageSites, subpageHero } from "@/app/content";
export const metadata: Metadata = {
title: "Di tích | VR360 Như Quỳnh",
@@ -21,7 +21,7 @@ export default function HeritageListPage() {
<section className="public-subpage-hero public-subpage-hero--compact relative isolate overflow-hidden border-b border-[var(--surface-border)]">
<Image
src={activeTour.gateImage}
src={subpageHero.diTich}
alt=""
fill
priority
@@ -52,8 +52,8 @@ export default function HeritageListPage() {
<div className="public-image-stage public-hero-photo public-stagger-item hidden lg:block">
<Image
src={activeTour.coverImage}
alt="Di sản văn hóa Xã Như Quỳnh"
src={subpageHero.diTichFeature}
alt="Kiệu rước dưới tán lọng vàng trong lễ hội Chùa Pháp Vân (Thái Lạc)"
fill
sizes="38vw"
className="object-cover"
+2 -2
View File
@@ -17,7 +17,7 @@ import { BackToTopButton } from "@/app/components/landing/BackToTopButton";
import { CountUp } from "@/app/components/landing/CountUp";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";
import { activeTour, antiquities, heritageSites, img } from "@/app/content";
import { activeTour, antiquities, heritageSites, img, subpageHero } from "@/app/content";
export const metadata: Metadata = {
title: "Giới thiệu | VR360 Như Quỳnh",
@@ -61,7 +61,7 @@ export default function AboutPage() {
<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 public-parallax-bg object-cover" />
<Image src={subpageHero.gioiThieu} alt="" fill priority sizes="100vw" className="public-subpage-hero-image public-parallax-bg 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 public-hero-rule max-w-4xl">
<p className="public-kicker">Giới thiệu</p>
+6 -2
View File
@@ -3,6 +3,7 @@ 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 { FestivalGallery } from "@/app/components/landing/FestivalGallery";
import { FestivalVideo } from "@/app/components/landing/FestivalVideo";
import { HeritageMarquee } from "@/app/components/landing/HeritageMarquee";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
@@ -84,8 +85,8 @@ export default function Home() {
<div className="public-image-stage public-stagger-item relative hidden min-h-[31rem] lg:block">
<Image
src={activeTour.coverImage}
alt="Không gian di sản Xã Như Quỳnh được số hóa VR360"
src={activeTour.heroFeatureImage}
alt="Nghi lễ rước kiệu trong lễ hội Chùa Thái Lạc (Pháp Vân tự), Xã Như Quỳnh"
fill
priority
sizes="42vw"
@@ -162,6 +163,9 @@ export default function Home() {
</div>
</section>
{/* Khoảnh khắc lễ hội — gallery bento ảnh thật, nối tiếp mạch "Lễ hội & văn hóa" */}
<FestivalGallery />
<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">
<p className="public-kicker">Giới thiệu nhanh</p>
+1 -1
View File
@@ -158,7 +158,7 @@ const seeds: SceneSeed[] = [
id: "video-le-hoi-cau-mua",
title: "Lễ hội cầu mưa",
eyebrow: EYEBROW,
yaw: 119,
yaw: 120,
pitch: 2,
iconType: "video",
video: {