From bca3d83bb416bed58977c49ad042e78ff78a7ad9 Mon Sep 17 00:00:00 2001 From: Zeeshan Khan Date: Tue, 2 Jun 2026 15:46:00 +0200 Subject: [PATCH] feat: add 3s scroll-pause video auto-play to homepage Signature Menu + update Menu page to 3s - Duplicated mobile scroll-pause logic (3s timeout) to homepage Signature Menu (using .signature-card + .relative.h-48 structure, with video support added to signatureDishes). - Updated both /menu and homepage Signature Menu from 2s to 3s pause before playing video on scroll stop. - Added onMouse handlers for desktop hover consistency on signature cards. - Maintains posters while scrolling, plays video after 3s pause on item. - Desktop hover behavior preserved. - Builds cleanly. --- app/menu/page.tsx | 4 +- app/page.tsx | 219 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 201 insertions(+), 22 deletions(-) diff --git a/app/menu/page.tsx b/app/menu/page.tsx index 016de24..3f9c33a 100644 --- a/app/menu/page.tsx +++ b/app/menu/page.tsx @@ -68,7 +68,7 @@ export default function MenuPage() { window.scrollTo({ top: 220, behavior: "smooth" }); }; - // Mobile video auto-play on scroll pause (2s) + // Mobile video auto-play on scroll pause (3s) useEffect(() => { const updateMobile = () => setIsMobile(window.innerWidth < 768); updateMobile(); @@ -149,7 +149,7 @@ export default function MenuPage() { if (focusedId) { playMobileVideo(focusedId); } - }, 2000); + }, 3000); }; window.addEventListener("scroll", onScrollOrTouch, { passive: true }); window.addEventListener("touchmove", onScrollOrTouch, { passive: true }); diff --git a/app/page.tsx b/app/page.tsx index a17677b..dcf974a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -25,6 +25,67 @@ export default function ShahiKitchenHomepage() { const t = getTranslation(language); const [menuFilter, setMenuFilter] = useState<"All" | "Rice" | "Curry" | "Meat" | "Street" | "Roll" | "Sweet">("All"); + const [isMobile, setIsMobile] = useState(false); + + // Mobile video helpers for Signature Menu (duplicated logic, 3s pause) + const playMobileVideoSignature = (id: string) => { + document.querySelectorAll(".signature-card").forEach((card) => { + if (card.dataset.id !== id) return; + const media = card.querySelector(".relative.h-48"); + if (!media) return; + const img = media.querySelector("img"); + const video = media.querySelector("video"); + const gradient = media.querySelector(".absolute.inset-0.bg-gradient-to-b"); + if (img) img.style.opacity = "0"; + if (video) { + video.style.opacity = "1"; + video.play().catch(() => {}); + } + if (gradient) gradient.style.opacity = "0"; + }); + }; + + const pauseAllMobileVideosSignature = () => { + document.querySelectorAll(".signature-card").forEach((card) => { + const media = card.querySelector(".relative.h-48"); + if (!media) return; + const img = media.querySelector("img"); + const video = media.querySelector("video"); + const gradient = media.querySelector(".absolute.inset-0.bg-gradient-to-b"); + if (img) img.style.opacity = ""; + if (video) { + video.style.opacity = "0"; + video.pause(); + video.currentTime = 0; + } + if (gradient) gradient.style.opacity = ""; + }); + }; + + const findFocusedVideoItemSignature = (): string | null => { + const cards = document.querySelectorAll('.signature-card[data-has-video="true"]'); + let bestId: string | null = null; + let bestScore = 0; + const vh = window.innerHeight; + const viewportCenter = vh / 2; + cards.forEach((card) => { + const rect = card.getBoundingClientRect(); + if (rect.bottom <= 0 || rect.top >= vh) return; + const visibleTop = Math.max(rect.top, 0); + const visibleBottom = Math.min(rect.bottom, vh); + const visibleHeight = visibleBottom - visibleTop; + const intersectionRatio = visibleHeight / rect.height; + const cardCenter = rect.top + rect.height / 2; + const distFromCenter = Math.abs(cardCenter - viewportCenter); + const centerScore = Math.max(0, 1 - distFromCenter / (vh / 2)); + const score = intersectionRatio * 0.6 + centerScore * 0.4; + if (score > bestScore && score > 0.15) { + bestScore = score; + bestId = card.dataset.id || null; + } + }); + return bestId; + }; // Lenis smooth scroll useEffect(() => { @@ -38,6 +99,47 @@ export default function ShahiKitchenHomepage() { return () => lenis.destroy(); }, []); + // Mobile detection for signature menu video logic + useEffect(() => { + const updateMobile = () => setIsMobile(window.innerWidth < 768); + updateMobile(); + window.addEventListener("resize", updateMobile); + return () => window.removeEventListener("resize", updateMobile); + }, []); + + useEffect(() => { + if (!isMobile) { + pauseAllMobileVideosSignature(); + return; + } + let scrollTimeout: ReturnType | null = null; + const onScrollOrTouch = () => { + pauseAllMobileVideosSignature(); + if (scrollTimeout) clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { + const focusedId = findFocusedVideoItemSignature(); + if (focusedId) { + playMobileVideoSignature(focusedId); + } + }, 3000); + }; + window.addEventListener("scroll", onScrollOrTouch, { passive: true }); + window.addEventListener("touchmove", onScrollOrTouch, { passive: true }); + // Initial play after load on mobile (if not scrolling) + const initialTimeout = setTimeout(() => { + const firstFocused = findFocusedVideoItemSignature(); + if (firstFocused) { + playMobileVideoSignature(firstFocused); + } + }, 1500); + return () => { + window.removeEventListener("scroll", onScrollOrTouch); + window.removeEventListener("touchmove", onScrollOrTouch); + if (scrollTimeout) clearTimeout(scrollTimeout); + clearTimeout(initialTimeout); + }; + }, [isMobile]); + const signatureDishes = [ { id: "chicken-biryani", @@ -46,7 +148,8 @@ export default function ShahiKitchenHomepage() { price: 149, time: "32 min", desc: "Fragrant aged basmati layered with spiced chicken, saffron & caramelized onions", - image: "chicken-biryani-poster.jpg" + image: "chicken-biryani-poster.jpg", + video: "chicken-biryani.mp4" }, { id: "bong-nihari", @@ -55,7 +158,8 @@ export default function ShahiKitchenHomepage() { price: 199, time: "45 min", desc: "Slow-cooked beef shank in rich aromatic gravy with ginger, lemon & fresh naan", - image: "bong-nihari-poster.jpg" + image: "bong-nihari-poster.jpg", + video: "bong-nihari.mp4" }, { id: "panipuri", @@ -64,7 +168,8 @@ export default function ShahiKitchenHomepage() { price: 69, time: "15 min", desc: "Crispy hollow puris filled with spiced chickpeas, potatoes & tangy tamarind water", - image: "panipuri-poster.jpg" + image: "panipuri-poster.jpg", + video: "panipuri.mp4" }, { id: "lamm-palak", @@ -73,7 +178,8 @@ export default function ShahiKitchenHomepage() { price: 179, time: "30 min", desc: "Tender lamb cooked with fresh spinach in a flavorful mild gravy", - image: "lamm-palak-poster.jpg" + image: "lamm-palak-poster.jpg", + video: "lamm-palak.mp4" }, { id: "chicken-karahi", @@ -82,7 +188,8 @@ export default function ShahiKitchenHomepage() { price: 149, time: "28 min", desc: "Wok-tossed chicken in a robust tomato, chili & ginger gravy", - image: "chicken-karahi-poster.jpg" + image: "chicken-karahi-poster.jpg", + video: "chicken-karahi.mp4" }, { id: "chicken-haleem", @@ -91,7 +198,8 @@ export default function ShahiKitchenHomepage() { price: 149, time: "35 min", desc: "Slow-cooked shredded chicken with lentils, wheat & aromatic spices", - image: "chicken-haleem-poster.jpg" + image: "chicken-haleem-poster.jpg", + video: "chicken-haleem.mp4" }, { id: "tikka-boti-roll", @@ -100,7 +208,8 @@ export default function ShahiKitchenHomepage() { price: 99, time: "20 min", desc: "Juicy chicken tikka wrapped in soft naan with mint chutney & onions", - image: "tikka-boti-roll-poster.jpg" + image: "tikka-boti-roll-poster.jpg", + video: "tikka-boti-roll.mp4" }, { id: "jalebi", @@ -109,7 +218,8 @@ export default function ShahiKitchenHomepage() { price: 119, time: "12 min", desc: "Crispy golden saffron spirals soaked in fragrant sugar syrup", - image: "jalebi-poster.jpg" + image: "jalebi-poster.jpg", + video: "jalebi.mp4" }, { id: "kulfi", @@ -118,7 +228,8 @@ export default function ShahiKitchenHomepage() { price: 39, time: "10 min", desc: "Traditional frozen milk dessert with cardamom, pistachio & saffron", - image: "kulfi-poster.jpg" + image: "kulfi-poster.jpg", + video: "kulfi.mp4" }, ]; @@ -252,21 +363,89 @@ export default function ShahiKitchenHomepage() { {filtered.map((dish, index) => ( addDish(dish)} className="signature-card group flex cursor-pointer flex-col overflow-hidden rounded-[2rem] border border-[#EDE6D9] bg-white active:scale-[0.985] active:border-[#c99a2e]/60 transition-transform touch-manipulation" > -
- {dish.name} -
- -
- {dish.price} kr -
+
{ + const video = e.currentTarget.querySelector("video"); + if (video) video.play().catch(() => {}); + } : undefined} + onMouseLeave={dish.video ? (e) => { + const video = e.currentTarget.querySelector("video"); + if (video) { + video.pause(); + video.currentTime = 0; + } + } : undefined} + > + {dish.video ? ( + <> + {/* Static Poster (first frame) */} + {dish.name} { + const base = (dish.video || dish.image || "").replace(".mp4", "").replace(/-optimized/g, "").replace(/-poster/g, ""); + const fallbacks = [ + `/images/dishes/${base}-poster.jpg`, + `/images/dishes/${base}.jpg`, + ].filter(Boolean); + let i = 0; + const tryNext = () => { + if (i < fallbacks.length) { + (e.target as HTMLImageElement).src = fallbacks[i++]; + } + }; + tryNext(); + }} + /> + {/* Video for mobile scroll-pause and desktop hover */} + +
+
+ {dish.price} kr +
+ + ) : ( + <> + {dish.name} +
+
+ {dish.price} kr +
+ + )}