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.
This commit is contained in:
+2
-2
@@ -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 });
|
||||
|
||||
+190
-11
@@ -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<HTMLElement>(".signature-card").forEach((card) => {
|
||||
if (card.dataset.id !== id) return;
|
||||
const media = card.querySelector<HTMLElement>(".relative.h-48");
|
||||
if (!media) return;
|
||||
const img = media.querySelector<HTMLImageElement>("img");
|
||||
const video = media.querySelector<HTMLVideoElement>("video");
|
||||
const gradient = media.querySelector<HTMLElement>(".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<HTMLElement>(".signature-card").forEach((card) => {
|
||||
const media = card.querySelector<HTMLElement>(".relative.h-48");
|
||||
if (!media) return;
|
||||
const img = media.querySelector<HTMLImageElement>("img");
|
||||
const video = media.querySelector<HTMLVideoElement>("video");
|
||||
const gradient = media.querySelector<HTMLElement>(".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<HTMLElement>('.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<typeof setTimeout> | 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) => (
|
||||
<motion.div
|
||||
key={dish.id}
|
||||
data-id={dish.id}
|
||||
data-has-video={!!dish.video}
|
||||
layout
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="relative h-48 overflow-hidden bg-[#F2EDE4]">
|
||||
<div
|
||||
className="relative h-48 overflow-hidden bg-[#F2EDE4]"
|
||||
onMouseEnter={dish.video ? (e) => {
|
||||
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) */}
|
||||
<img
|
||||
src={`/images/dishes/${dish.image}`}
|
||||
alt={dish.name}
|
||||
className="signature-image absolute inset-0 w-full h-full object-cover transition-opacity duration-150"
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
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 */}
|
||||
<video
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="metadata"
|
||||
className="absolute inset-0 w-full h-full object-cover opacity-0 group-hover:opacity-100 transition-opacity duration-150"
|
||||
>
|
||||
<source
|
||||
src={`/videos/${dish.video.replace(".mp4", "-optimized.webm")}`}
|
||||
type="video/webm"
|
||||
/>
|
||||
<source
|
||||
src={`/videos/${dish.video.replace(".mp4", "-optimized.mp4")}`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
<source
|
||||
src={`/videos/${dish.video.replace(".mp4", ".webm")}`}
|
||||
type="video/webm"
|
||||
/>
|
||||
<source src={`/videos/${dish.video}`} type="video/mp4" />
|
||||
</video>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/5 via-transparent to-black/25 group-hover:opacity-0 transition-opacity" />
|
||||
<div className="absolute top-4 right-4 px-4 py-1 rounded-full bg-white/95 text-[#B38B4D] text-sm font-medium tracking-tight shadow-sm border border-[#EDE6D9]">
|
||||
{dish.price} kr
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src={`/images/dishes/${dish.image}`}
|
||||
alt={dish.name}
|
||||
className="signature-image absolute inset-0 w-full h-full object-cover transition-transform duration-700"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/5 via-transparent to-black/25" />
|
||||
|
||||
<div className="absolute top-4 right-4 px-4 py-1 rounded-full bg-white/95 text-[#B38B4D] text-sm font-medium tracking-tight shadow-sm border border-[#EDE6D9]">
|
||||
{dish.price} kr
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-5 flex-1 flex flex-col">
|
||||
|
||||
Reference in New Issue
Block a user