Merge version_3_1776752954568 into main

Merge version_3_1776752954568 into main
This commit was merged in pull request #2.
This commit is contained in:
2026-04-21 06:30:47 +00:00
2 changed files with 120 additions and 61 deletions

View File

@@ -3,7 +3,7 @@ import ContactSplitEmail from '@/components/sections/contact/ContactSplitEmail';
import FaqSimple from '@/components/sections/faq/FaqSimple';
import FeaturesTaggedCards from '@/components/sections/features/FeaturesTaggedCards';
import FooterSimpleMedia from '@/components/sections/footer/FooterSimpleMedia';
import HeroBillboardScroll from '@/components/sections/hero/HeroBillboardScroll';
import HeroBillboardCarousel from '@/components/sections/hero/HeroBillboardCarousel';
import NavbarCentered from '@/components/ui/NavbarCentered';
import ProductVariantCards from '@/components/sections/product/ProductVariantCards';
import TestimonialAvatarCard from '@/components/sections/testimonial/TestimonialAvatarCard';
@@ -40,19 +40,51 @@ export default function App() {
</div>
<div id="hero" data-section="hero">
<HeroBillboardScroll
tag="Fusion Takeout"
title="Seoul Meets Italy in Every Bite"
description="Experience the bold heat of Korea blended with the timeless comfort of Italian pasta. Order your fusion favorites for quick, hot takeout today."
primaryButton={{
text: "Browse Menu",
href: "#menu",
}}
secondaryButton={{
text: "Our Story",
href: "#about",
}}
imageSrc="http://img.b2bpic.net/free-photo/pasta-with-germany-sausage_1339-5598.jpg"
<HeroBillboardCarousel
slides={[
{
tag: "Fusion Takeout",
title: "Seoul Meets Italy in Every Bite",
description: "Experience the bold heat of Korea blended with the timeless comfort of Italian pasta. Order your fusion favorites for quick, hot takeout today.",
primaryButton: {
text: "Browse Menu",
href: "#menu",
},
secondaryButton: {
text: "Our Story",
href: "#about",
},
imageSrc: "http://img.b2bpic.net/free-photo/pasta-with-germany-sausage_1339-5598.jpg",
},
{
tag: "Signature Dishes",
title: "Discover Our Kimchi Carbonara",
description: "A creamy, spicy twist on a classic. The ultimate comfort food with a Korean kick. You have to try it to believe it.",
primaryButton: {
text: "View Dish",
href: "#menu",
},
secondaryButton: {
text: "Full Menu",
href: "#menu",
},
imageSrc: "http://img.b2bpic.net/free-photo/delicious-pasta-fork_23-2147749504.jpg",
},
{
tag: "Fast & Fresh",
title: "Hot, Fresh, and Ready For You",
description: "We prepare every dish with the freshest ingredients and deliver it piping hot to your door. Quality you can taste.",
primaryButton: {
text: "Order Delivery",
href: "#contact",
},
secondaryButton: {
text: "Why Choose Us",
href: "#features",
},
imageSrc: "http://img.b2bpic.net/free-photo/top-view-plastic-box-with-leftover-cookie_23-2148666825.jpg",
},
]}
/>
</div>

View File

@@ -1,69 +1,96 @@
"use client";
import useEmblaCarousel from "embla-carousel-react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { motion } from "motion/react";
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import { useCarouselControls } from "@/hooks/useCarouselControls";
type HeroBillboardCarouselProps = {
type Slide = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
items: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type HeroBillboardCarouselProps = {
slides: Slide[];
};
const HeroBillboardCarousel = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: HeroBillboardCarouselProps) => {
const duplicated = [...items, ...items, ...items, ...items];
const HeroBillboardCarousel = ({ slides }: HeroBillboardCarouselProps) => {
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true });
const { prevDisabled, nextDisabled, scrollPrev, scrollNext, scrollProgress } = useCarouselControls(emblaApi);
return (
<section
aria-label="Hero section"
className="flex flex-col items-center justify-center gap-8 w-full min-h-svh py-25"
>
<div className="flex flex-col items-center gap-3 w-content-width mx-auto text-center">
<span className="px-3 py-1 mb-1 text-sm card rounded">{tag}</span>
<TextAnimation
text={title}
variant="slide-up"
tag="h1"
className="text-6xl font-medium text-balance"
/>
<TextAnimation
text={description}
variant="slide-up"
tag="p"
className="text-base md:text-lg leading-tight text-balance"
/>
<div className="flex flex-wrap justify-center gap-3 mt-2">
<Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />
<Button text={secondaryButton.text} href={secondaryButton.href} variant="secondary" animate delay={0.1} />
<section aria-label="Hero section" className="relative">
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex">
{slides.map((slide, index) => (
<div className="flex-[0_0_100%] min-w-0 relative h-screen" key={index}>
<div className="absolute inset-0 bg-black/40 z-10" />
<ImageOrVideo
imageSrc={slide.imageSrc}
videoSrc={slide.videoSrc}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 z-20 flex flex-col justify-center items-center text-center text-foreground p-6">
<div className="w-content-width mx-auto flex flex-col items-center gap-3">
<span className="px-3 py-1 mb-1 text-sm bg-white/10 backdrop-blur-sm border border-white/20 rounded">{slide.tag}</span>
<TextAnimation
text={slide.title}
variant="slide-up"
tag="h1"
className="text-6xl font-medium text-balance"
/>
<TextAnimation
text={slide.description}
variant="slide-up"
tag="p"
className="text-base md:text-lg leading-tight text-balance max-w-3xl"
/>
<div className="flex flex-wrap justify-center gap-3 mt-2">
<Button text={slide.primaryButton.text} href={slide.primaryButton.href} variant="primary" animateImmediately />
<Button text={slide.secondaryButton.text} href={slide.secondaryButton.href} variant="secondary" animateImmediately delay={0.1} />
</div>
</div>
</div>
</div>
))}
</div>
</div>
<div className="w-content-width mx-auto overflow-hidden mask-fade-x">
<div className="flex w-max animate-marquee-horizontal" style={{ animationDuration: "60s" }}>
{duplicated.map((item, i) => (
<div key={i} className="shrink-0 w-60 md:w-75 2xl:w-80 aspect-4/5 mr-3 md:mr-5 p-1.5 card rounded-lg overflow-hidden">
<ImageOrVideo
imageSrc={item.imageSrc}
videoSrc={item.videoSrc}
className="w-full h-full rounded-lg object-cover"
/>
</div>
))}
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 z-20 w-content-width mx-auto flex items-center justify-between px-6">
<div className="flex items-center gap-3">
<button
onClick={scrollPrev}
disabled={prevDisabled}
className="size-10 flex items-center justify-center bg-white/10 backdrop-blur-sm border border-white/20 rounded-full text-foreground disabled:opacity-50 transition-opacity"
aria-label="Previous slide"
>
<ChevronLeft className="size-5" />
</button>
<button
onClick={scrollNext}
disabled={nextDisabled}
className="size-10 flex items-center justify-center bg-white/10 backdrop-blur-sm border border-white/20 rounded-full text-foreground disabled:opacity-50 transition-opacity"
aria-label="Next slide"
>
<ChevronRight className="size-5" />
</button>
</div>
<div className="w-full max-w-xs h-1 bg-white/20 rounded-full overflow-hidden">
<motion.div
className="h-full bg-background"
style={{ width: `${scrollProgress}%` }}
/>
</div>
</div>
</section>
);
};
export default HeroBillboardCarousel;
export default HeroBillboardCarousel;