583 lines
25 KiB
TypeScript
583 lines
25 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
import { gsap } from 'gsap';
|
||
import {
|
||
getMenuPosterCandidates,
|
||
getMenuPosterSrc,
|
||
getMenuVideoSources,
|
||
applyNextImageFallback,
|
||
} from '@/lib/assets';
|
||
import { menuCategories, allMenuItems, type MenuItem, type MenuCategory } from '@/lib/menu-data';
|
||
import { useLanguage } from '@/lib/language-context';
|
||
import { getTranslation } from '@/lib/translations';
|
||
import {
|
||
getMenuItemDescription,
|
||
getMenuItemName,
|
||
} from '@/application/i18n/menu-localization';
|
||
import type { Language } from '@/domain/language/entities';
|
||
|
||
interface ScreenDisplayProps {
|
||
screen: 1 | 2;
|
||
}
|
||
|
||
const SCREEN1_CATEGORY_IDS = ['street-food', 'vegetarian', 'meat', 'chicken', 'burger-sandwich'];
|
||
const SCREEN2_CATEGORY_IDS = ['pizza', 'naan-roll', 'sweets', 'drinks'];
|
||
|
||
function SpiceParticles() {
|
||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||
|
||
useEffect(() => {
|
||
const canvas = canvasRef.current;
|
||
if (!canvas) return;
|
||
|
||
const ctx = canvas.getContext('2d', { alpha: true });
|
||
if (!ctx) return;
|
||
|
||
let width = window.innerWidth;
|
||
let height = window.innerHeight;
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
|
||
const particles = Array.from({ length: 42 }, () => ({
|
||
x: Math.random() * width,
|
||
y: Math.random() * height * 0.9,
|
||
vx: (Math.random() - 0.5) * 0.22,
|
||
vy: -0.12 - Math.random() * 0.22,
|
||
size: 1.1 + Math.random() * 1.9,
|
||
alpha: 0.12 + Math.random() * 0.22,
|
||
phase: Math.random() * Math.PI * 2,
|
||
speed: 0.018 + Math.random() * 0.012,
|
||
}));
|
||
|
||
const onResize = () => {
|
||
width = window.innerWidth;
|
||
height = window.innerHeight;
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
};
|
||
window.addEventListener('resize', onResize);
|
||
|
||
let rafId: number;
|
||
|
||
const draw = () => {
|
||
ctx.clearRect(0, 0, width, height);
|
||
|
||
for (let i = 0; i < particles.length; i++) {
|
||
const p = particles[i];
|
||
p.x += p.vx + Math.sin(p.phase) * 0.07;
|
||
p.y += p.vy;
|
||
p.phase += p.speed;
|
||
|
||
if (p.y < -10) {
|
||
p.y = height * 0.92 + Math.random() * 40;
|
||
p.x = Math.random() * width;
|
||
}
|
||
if (p.x < 0) p.x = width;
|
||
if (p.x > width) p.x = 0;
|
||
|
||
ctx.fillStyle = `hsla(38, 68%, 52%, ${p.alpha})`;
|
||
ctx.beginPath();
|
||
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// occasional tiny sparkle
|
||
if (Math.random() < 0.014) {
|
||
ctx.fillStyle = `hsla(42, 90%, 78%, ${p.alpha * 0.9})`;
|
||
ctx.fillRect(p.x - 0.6, p.y - 0.6, 1.2, 1.2);
|
||
}
|
||
}
|
||
rafId = requestAnimationFrame(draw);
|
||
};
|
||
|
||
draw();
|
||
|
||
return () => {
|
||
cancelAnimationFrame(rafId);
|
||
window.removeEventListener('resize', onResize);
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<canvas
|
||
ref={canvasRef}
|
||
className="fixed inset-0 pointer-events-none z-[2] opacity-70 mix-blend-multiply"
|
||
/>
|
||
);
|
||
}
|
||
|
||
function MenuCard({
|
||
item,
|
||
isHighlighted,
|
||
language,
|
||
t,
|
||
}: {
|
||
item: MenuItem;
|
||
isHighlighted?: boolean;
|
||
language: Language;
|
||
t: ReturnType<typeof getTranslation>;
|
||
}) {
|
||
const poster = getMenuPosterSrc(item, 'optimized');
|
||
const displayName = getMenuItemName(language, item);
|
||
const desc = getMenuItemDescription(language, t, item);
|
||
|
||
return (
|
||
<motion.div
|
||
className={`group relative flex flex-col overflow-hidden rounded-2xl border bg-white transition-all duration-300 ${
|
||
isHighlighted
|
||
? 'border-[#B38B4D] ring-2 ring-[#B38B4D]/70 shadow-xl scale-[1.015] z-10'
|
||
: 'border-[#EDE6D9] hover:border-[#B38B4D]/40'
|
||
}`}
|
||
animate={isHighlighted ? { scale: 1.015 } : { scale: 1 }}
|
||
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
|
||
>
|
||
<div className="relative h-36 bg-[#F2EDE4] overflow-hidden">
|
||
<motion.img
|
||
src={poster}
|
||
alt={displayName}
|
||
className="absolute inset-0 w-full h-full object-cover"
|
||
animate={{ scale: [1, 1.018, 1] }}
|
||
transition={{ duration: 8.5, repeat: Infinity, ease: 'easeInOut' }}
|
||
onError={(e) => {
|
||
applyNextImageFallback(
|
||
e.currentTarget,
|
||
getMenuPosterCandidates(item),
|
||
e.currentTarget.src
|
||
);
|
||
}}
|
||
/>
|
||
<div className="absolute inset-0 bg-gradient-to-b from-black/5 via-transparent to-black/35" />
|
||
|
||
{/* Gold price pill */}
|
||
<div className="absolute top-3 right-3 px-3 py-1 rounded-full bg-white/95 text-[#B38B4D] text-lg font-semibold tracking-tight shadow-sm border border-[#EDE6D9]">
|
||
{item.price} <span className="text-[10px] font-normal tracking-widest align-super">KR</span>
|
||
</div>
|
||
|
||
{item.isVegetarian && (
|
||
<div className="absolute top-3 left-3 text-[10px] px-2.5 py-0.5 rounded-full bg-[#3F5C4A]/90 text-white tracking-[1px]">
|
||
VEG
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="p-3.5 flex-1 flex flex-col">
|
||
<div className="font-medium tracking-[-0.3px] text-[15px] leading-tight text-[#2C2A26] mb-1 line-clamp-1 group-hover:text-[#B38B4D] transition-colors">
|
||
{displayName}
|
||
</div>
|
||
<p className="text-[#6B665F] text-[12px] leading-snug line-clamp-2 flex-1">
|
||
{desc}
|
||
</p>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
}
|
||
|
||
export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
|
||
const { language } = useLanguage();
|
||
const t = getTranslation(language);
|
||
|
||
const screenLabel = screen === 1 ? t.screen.display1 : t.screen.display2;
|
||
const isScreen1 = screen === 1;
|
||
|
||
// Filtered data for this screen
|
||
const screenCategories = useMemo(() => {
|
||
const ids = isScreen1 ? SCREEN1_CATEGORY_IDS : SCREEN2_CATEGORY_IDS;
|
||
return menuCategories.filter((c) => ids.includes(c.id));
|
||
}, [isScreen1]);
|
||
|
||
// Cycle through ALL items for this screen, one by one in the large hero (not just signatures)
|
||
const cyclingItems = useMemo(() => {
|
||
return screenCategories.flatMap((category) => category.items);
|
||
}, [screenCategories]);
|
||
|
||
// Cycling through all menu items (large top animation)
|
||
const [currentIndex, setCurrentIndex] = useState(0);
|
||
const current = cyclingItems[currentIndex] || cyclingItems[0];
|
||
|
||
const heroTiltRef = useRef<HTMLDivElement>(null);
|
||
|
||
// Auto cycle through every item (large top hero shows all, one by one)
|
||
const CYCLE_INTERVAL_MS = 8200;
|
||
useEffect(() => {
|
||
const id = setInterval(() => {
|
||
setCurrentIndex((i) => (i + 1) % cyclingItems.length);
|
||
}, CYCLE_INTERVAL_MS);
|
||
return () => clearInterval(id);
|
||
}, [cyclingItems.length]);
|
||
|
||
// Beautiful 3D tilt on hero (like signature cards on homepage)
|
||
useEffect(() => {
|
||
const el = heroTiltRef.current;
|
||
if (!el) return;
|
||
|
||
const onMouseMove = (e: MouseEvent) => {
|
||
const rect = el.getBoundingClientRect();
|
||
const x = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
|
||
const y = ((e.clientY - rect.top) / rect.height - 0.5) * 2;
|
||
|
||
gsap.to(el, {
|
||
rotationY: x * 7,
|
||
rotationX: -y * 5.5,
|
||
transformPerspective: 1400,
|
||
duration: 0.36,
|
||
ease: 'power2.out',
|
||
overwrite: true,
|
||
});
|
||
};
|
||
|
||
const onMouseLeave = () => {
|
||
gsap.to(el, {
|
||
rotationY: 0,
|
||
rotationX: 0,
|
||
duration: 1.35,
|
||
ease: 'elastic.out(1, 0.48)',
|
||
});
|
||
};
|
||
|
||
el.addEventListener('mousemove', onMouseMove);
|
||
el.addEventListener('mouseleave', onMouseLeave);
|
||
|
||
return () => {
|
||
el.removeEventListener('mousemove', onMouseMove);
|
||
el.removeEventListener('mouseleave', onMouseLeave);
|
||
};
|
||
}, []);
|
||
|
||
// Professional kiosk mode for screen1/screen2:
|
||
// Force clean fullscreen-like window (no side scrollbars) even in F11 or kiosk browser.
|
||
// Applies to html/body so the window itself has no scrollbar.
|
||
// User zooms the browser in/out to change reel density (# visible items) instead of scrolling.
|
||
// Cleanup restores previous state (good if ever navigating away in dev).
|
||
useEffect(() => {
|
||
const htmlEl = document.documentElement;
|
||
const bodyEl = document.body;
|
||
|
||
const prevHtmlClass = htmlEl.className;
|
||
const prevBodyClass = bodyEl.className;
|
||
const prevHtmlOverflow = htmlEl.style.overflow;
|
||
const prevBodyOverflow = bodyEl.style.overflow;
|
||
|
||
htmlEl.classList.add('screen-kiosk');
|
||
bodyEl.classList.add('screen-kiosk');
|
||
htmlEl.style.overflow = 'hidden';
|
||
bodyEl.style.overflow = 'hidden';
|
||
|
||
return () => {
|
||
// restore
|
||
htmlEl.style.overflow = prevHtmlOverflow;
|
||
bodyEl.style.overflow = prevBodyOverflow;
|
||
// remove only if we added (simple: toggle off)
|
||
htmlEl.classList.remove('screen-kiosk');
|
||
bodyEl.classList.remove('screen-kiosk');
|
||
};
|
||
}, []);
|
||
|
||
const currentDisplayName = current ? getMenuItemName(language, current) : '';
|
||
const translatedDesc = current ? getMenuItemDescription(language, t, current) : '';
|
||
|
||
// =====================================================
|
||
// CONTINUOUS MENU REEL (one line, loop like a film reel) - items move LEFT
|
||
// Enter from RIGHT, exit LEFT. Number of visible items is dynamic (more when zoom out, fewer when zoom in).
|
||
// The reel viewport is always full width (edge to edge, no max-w), no side whitespace.
|
||
// Posters only (no videos) for seamless movement. Numbers for easy ordering (screen1:1-26, screen2:27+).
|
||
// CSS marquee for stable seamless poster reel.
|
||
// Card sizes fixed px for consistent distance viewing; # visible auto-adjusts to current container width on zoom.
|
||
// Sized large for 43" TV distance viewing in restaurant.
|
||
// =====================================================
|
||
const allMenuItemsForReel = screenCategories.flatMap((c) => c.items);
|
||
const numItems = allMenuItemsForReel.length;
|
||
const startNumber = isScreen1 ? 1 : 27;
|
||
// Duplicate for seamless infinite loop (scroll 50% to repeat perfectly)
|
||
const reelItems = [...allMenuItemsForReel, ...allMenuItemsForReel];
|
||
|
||
// Card width tuned for 43" screen (~1920px), 3 visible + gaps ~1872px at default zoom.
|
||
// Poster 320px + bottom ~70px (py-5 + one-line name+right price) so cards fill the ~390px reel band vertically.
|
||
const REEL_CARD_WIDTH = 620; // px
|
||
const REEL_CARD_GAP = 16; // px (Tailwind gap-4)
|
||
|
||
// ReelCard: large poster + number+name on left, price on right (opposite side, same line) so price is always clearly visible as cards move left in the reel. Sized big for 43" TV distance viewing.
|
||
function ReelCard({ item, number }: { item: MenuItem; number: number }) {
|
||
const numStr = String(number).padStart(2, '0');
|
||
const reelName = getMenuItemName(language, item);
|
||
|
||
return (
|
||
<div
|
||
className="flex-shrink-0 bg-white rounded-3xl overflow-hidden border border-[#EDE6D9] shadow-xl flex flex-col"
|
||
style={{ width: `${REEL_CARD_WIDTH}px`, transform: 'translateZ(0)', backfaceVisibility: 'hidden' }}
|
||
>
|
||
<div className="relative bg-[#F2EDE4]" style={{ height: '320px' }}>
|
||
<motion.img
|
||
src={getMenuPosterSrc(item, 'optimized')}
|
||
alt={reelName}
|
||
className="w-full h-full object-cover"
|
||
animate={{ scale: [1, 1.015, 1] }}
|
||
transition={{ duration: 6, repeat: Infinity, ease: 'easeInOut' }}
|
||
/>
|
||
<div className="absolute inset-0 bg-gradient-to-b from-black/5 to-black/25" />
|
||
{/* Prominent number badge for easy ordering by number e.g. "give me number 13" */}
|
||
<div className="absolute top-2 left-2 z-10 px-3.5 py-1 rounded-full bg-black/75 text-[#E8D4A3] text-3xl font-mono font-bold tracking-[3px] shadow-lg border border-[#E8D4A3]/40">
|
||
{numStr}
|
||
</div>
|
||
</div>
|
||
<div className="px-4 py-5 flex items-baseline justify-between gap-4">
|
||
<div className="font-serif text-3xl tracking-[-0.5px] leading-none text-[#2C2A26]">
|
||
{numStr}. {reelName}
|
||
</div>
|
||
<div className="text-[#B38B4D] text-2xl font-semibold tracking-tight whitespace-nowrap">
|
||
{item.price} kr
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="h-screen bg-[#F8F5F0] text-[#2C2A26] overflow-hidden select-none flex flex-col screen-root">
|
||
<SpiceParticles />
|
||
|
||
{/* Elegant top bar */}
|
||
<div className="relative z-20 h-14 border-b border-[#EDE6D9]/70 bg-[#F8F5F0]/95 backdrop-blur-md flex items-center px-9 text-sm flex-none">
|
||
<div className="flex items-center gap-3">
|
||
<div className="font-serif text-2xl tracking-[-1.5px] text-[#2C2A26]">SHAHI KITCHEN</div>
|
||
<div className="text-[10px] px-2.5 py-px rounded-full border border-[#B38B4D]/50 text-[#B38B4D] tracking-[2.5px] font-medium">
|
||
{screenLabel}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex-1" />
|
||
|
||
<div className="flex items-center gap-6 text-[#6B665F] tracking-[1px] text-xs">
|
||
<div>ASKIM • BACKAPLAN</div>
|
||
<div className="h-2.5 w-px bg-[#EDE6D9]" />
|
||
<div>{t.screen.prepared.toUpperCase()}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* HERO — Large cycling animation showing ALL items one by one (not just signatures).
|
||
flex-1 so it absorbs extra viewport height on tall displays / when zoomed out.
|
||
On short effective viewports (zoomed in) it shrinks gracefully; text sized for fit. */}
|
||
<div className="flex-1 min-h-[260px] relative z-10 bg-[#101724] flex items-center justify-center overflow-hidden">
|
||
<div
|
||
ref={heroTiltRef}
|
||
className="absolute inset-0 will-change-transform"
|
||
style={{ transformStyle: 'preserve-3d' }}
|
||
>
|
||
{/* Media switches with crossfade + fresh start each cycle. Video when available, else large animated poster (Ken Burns). */}
|
||
<AnimatePresence mode="wait">
|
||
{current && (
|
||
current.video ? (
|
||
<motion.div
|
||
key={`${current.id}-video`}
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
transition={{ duration: 0.45 }}
|
||
className="absolute inset-0"
|
||
>
|
||
<video
|
||
className="absolute inset-0 w-full h-full object-cover"
|
||
autoPlay
|
||
muted
|
||
loop
|
||
playsInline
|
||
preload="auto"
|
||
>
|
||
{(() => {
|
||
const srcs = getMenuVideoSources(current.video);
|
||
return (
|
||
<>
|
||
{srcs.webm && <source src={srcs.webm} type="video/webm" />}
|
||
{srcs.mp4 && <source src={srcs.mp4} type="video/mp4" />}
|
||
{srcs.fallback && <source src={srcs.fallback} type="video/mp4" />}
|
||
</>
|
||
);
|
||
})()}
|
||
</video>
|
||
</motion.div>
|
||
) : (
|
||
<motion.img
|
||
key={`${current.id}-poster`}
|
||
src={getMenuPosterSrc(current, 'optimized')}
|
||
alt={currentDisplayName}
|
||
className="absolute inset-0 w-full h-full object-cover"
|
||
initial={{ opacity: 0, scale: 1 }}
|
||
animate={{ opacity: 1, scale: [1, 1.06, 1] }}
|
||
exit={{ opacity: 0 }}
|
||
transition={{ opacity: { duration: 0.45 }, scale: { duration: 8, repeat: Infinity, ease: 'easeInOut' } }}
|
||
onError={(e) => {
|
||
applyNextImageFallback(
|
||
e.currentTarget,
|
||
getMenuPosterCandidates(current),
|
||
e.currentTarget.src
|
||
);
|
||
}}
|
||
/>
|
||
)
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
{/* Rich overlays for text legibility + luxury feel (stay on top of changing media) */}
|
||
<div className="absolute inset-0 bg-gradient-to-b from-black/25 via-black/15 to-black/55" />
|
||
<div className="absolute inset-0 bg-[radial-gradient(#B38B4D_0.6px,transparent_1px)] bg-[length:5px_5px] opacity-[0.035]" />
|
||
</div>
|
||
|
||
{/* Animated dish info */}
|
||
<div className="relative z-10 max-w-4xl px-10 text-white">
|
||
<AnimatePresence mode="wait">
|
||
{current && (
|
||
<motion.div
|
||
key={current.id}
|
||
initial={{ opacity: 0, y: 18 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -14 }}
|
||
transition={{ duration: 0.55, ease: [0.21, 0.92, 0.26, 1] }}
|
||
>
|
||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-1 text-xs tracking-[3.5px] mb-4 border border-white/20">
|
||
{t.screen.menuTitle}
|
||
</div>
|
||
|
||
<h1 className="font-serif text-[56px] md:text-[64px] leading-[0.88] tracking-[-3.2px] mb-2 text-[#B38B4D] drop-shadow-[0_0_10px_rgba(179,139,77,0.85)]">
|
||
{currentDisplayName}
|
||
</h1>
|
||
|
||
<div className="flex items-baseline gap-2 mb-4">
|
||
<div className="text-5xl font-medium tracking-[-1.5px] text-[#E8D4A3]">
|
||
{current.price}
|
||
</div>
|
||
<div className="text-2xl text-white/70 tracking-[1px]">KR</div>
|
||
</div>
|
||
|
||
<p className="max-w-[620px] text-[17px] leading-tight text-white/90 tracking-[-0.1px] drop-shadow">
|
||
{translatedDesc}
|
||
</p>
|
||
|
||
<div className="mt-5 text-[11px] tracking-[3px] text-white/60">
|
||
{t.screen.prepared}
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
|
||
{/* Cycle progress + counter (clean for many items) */}
|
||
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-20 flex items-center gap-3 text-white/70 text-xs tracking-[2px]">
|
||
<div>{currentIndex + 1} / {cyclingItems.length}</div>
|
||
<div className="w-32 h-px bg-white/20 rounded overflow-hidden">
|
||
<motion.div
|
||
key={currentIndex}
|
||
className="h-px bg-[#E8D4A3]"
|
||
initial={{ width: '0%' }}
|
||
animate={{ width: '100%' }}
|
||
transition={{ duration: CYCLE_INTERVAL_MS / 1000, ease: 'linear' }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* MENU REEL - continuous LEFT-moving reel (items move left: enter from right, exit left), entire menu in one line.
|
||
Dynamic # of items visible based on zoom (more when zoomed out). Always full width edge-to-edge (no side whitespace).
|
||
~8s per item, seamless loop with posters only. Fixed band height so layout sums <=100vh with flex-1 hero. */}
|
||
<div className="relative z-10 bg-[#F8F5F0] border-t border-[#EDE6D9]/60 overflow-hidden w-full flex-none">
|
||
<div className="max-w-[1680px] mx-auto px-4 py-1.5">
|
||
<div className="text-[#B38B4D] text-xs tracking-[4px] font-medium mb-1">THE MENU • REEL</div>
|
||
</div>
|
||
|
||
{/* Full-width viewport for the reel; cards will fill based on container width. Height tuned to match card (poster + bottom text area) so items fill the band vertically with no large empty space under as they rotate left. */}
|
||
<div style={{ height: '390px' }} className="overflow-hidden w-full">
|
||
<div
|
||
className="menu-reel-track flex gap-4"
|
||
style={{
|
||
width: `${reelItems.length * (REEL_CARD_WIDTH + REEL_CARD_GAP)}px`,
|
||
animationDuration: `${numItems * 8}s`
|
||
}}
|
||
>
|
||
{reelItems.map((item, index) => {
|
||
const displayNumber = ((index % numItems) + startNumber);
|
||
return (
|
||
<ReelCard key={`${item.id}-${index}`} item={item} number={displayNumber} />
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* COMBO MEALS & DISCOUNT OFFERS - compact vertical footprint so total page height always <= 100vh in fullscreen (no scrollbar) */}
|
||
<div className="relative z-10 bg-[#F8F5F0] py-3 border-t border-[#EDE6D9]/60 flex-none">
|
||
<div className="max-w-[1680px] mx-auto px-8">
|
||
<div className="flex items-center gap-4 mb-3">
|
||
<div className="text-[#B38B4D] text-xs tracking-[3.5px] font-medium">COMBO MEALS & OFFERS</div>
|
||
<div className="flex-1 h-px bg-[#B38B4D]/25" />
|
||
<div className="text-xs text-[#6B665F]">Ask at counter by offer number</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
{/* Offer 1 */}
|
||
<div className="bg-white rounded-3xl border border-[#EDE6D9] shadow-xl p-4 flex flex-col">
|
||
<div className="flex items-center justify-between mb-1.5">
|
||
<div className="text-[#B38B4D] text-xs tracking-[2px] font-semibold">OFFER 1</div>
|
||
<div className="text-[10px] px-2 py-0.5 rounded-full bg-[#B38B4D] text-white tracking-wider">BEST VALUE</div>
|
||
</div>
|
||
<div className="font-serif text-xl tracking-tight text-[#101724] leading-tight">SHAHI FAMILY COMBO</div>
|
||
<div className="text-xs text-[#6B665F] mt-0.5 flex-1">2 Chicken Curries + 2 Veg Curries + 4 Naan + 2 Rice + 4 Drinks</div>
|
||
<div className="mt-2 flex items-baseline gap-2">
|
||
<span className="text-3xl font-semibold text-[#B38B4D]">799 kr</span>
|
||
<span className="text-base line-through text-[#8A8478]">999 kr</span>
|
||
</div>
|
||
<div className="text-[10px] text-[#B38B4D] font-medium">SAVE 200 KR • Serves 4</div>
|
||
</div>
|
||
|
||
{/* Offer 2 */}
|
||
<div className="bg-white rounded-3xl border border-[#EDE6D9] shadow-xl p-4 flex flex-col">
|
||
<div className="flex items-center justify-between mb-1.5">
|
||
<div className="text-[#B38B4D] text-xs tracking-[2px] font-semibold">OFFER 2</div>
|
||
</div>
|
||
<div className="font-serif text-xl tracking-tight text-[#101724] leading-tight">LUNCH THALI SPECIAL</div>
|
||
<div className="text-xs text-[#6B665F] mt-0.5 flex-1">Any Curry + Rice + 2 Naan + Salad + Drink (11am–3pm)</div>
|
||
<div className="mt-2 flex items-baseline gap-2">
|
||
<span className="text-3xl font-semibold text-[#B38B4D]">149 kr</span>
|
||
<span className="text-base line-through text-[#8A8478]">189 kr</span>
|
||
</div>
|
||
<div className="text-[10px] text-[#B38B4D] font-medium">SAVE 40 KR • Mon–Fri only</div>
|
||
</div>
|
||
|
||
{/* Offer 3 */}
|
||
<div className="bg-white rounded-3xl border border-[#EDE6D9] shadow-xl p-4 flex flex-col">
|
||
<div className="flex items-center justify-between mb-1.5">
|
||
<div className="text-[#B38B4D] text-xs tracking-[2px] font-semibold">OFFER 3</div>
|
||
</div>
|
||
<div className="font-serif text-xl tracking-tight text-[#101724] leading-tight">PIZZA & ROLL DEAL</div>
|
||
<div className="text-xs text-[#6B665F] mt-0.5 flex-1">Any 2 Pizzas + Any 2 Rolls + 2 Drinks</div>
|
||
<div className="mt-2 flex items-baseline gap-2">
|
||
<span className="text-3xl font-semibold text-[#B38B4D]">229 kr</span>
|
||
<span className="text-base line-through text-[#8A8478]">299 kr</span>
|
||
</div>
|
||
<div className="text-[10px] text-[#B38B4D] font-medium">SAVE 70 KR • Great for sharing</div>
|
||
</div>
|
||
|
||
{/* Offer 4 */}
|
||
<div className="bg-white rounded-3xl border border-[#EDE6D9] shadow-xl p-4 flex flex-col">
|
||
<div className="flex items-center justify-between mb-1.5">
|
||
<div className="text-[#B38B4D] text-xs tracking-[2px] font-semibold">OFFER 4</div>
|
||
<div className="text-[10px] px-2 py-0.5 rounded-full bg-[#3F5C4A] text-white tracking-wider">VEG</div>
|
||
</div>
|
||
<div className="font-serif text-xl tracking-tight text-[#101724] leading-tight">VEG FEAST FOR 4</div>
|
||
<div className="text-xs text-[#6B665F] mt-0.5 flex-1">4 Veg Curries + 4 Naan + 2 Rice + 4 Drinks</div>
|
||
<div className="mt-2 flex items-baseline gap-2">
|
||
<span className="text-3xl font-semibold text-[#B38B4D]">599 kr</span>
|
||
<span className="text-base line-through text-[#8A8478]">749 kr</span>
|
||
</div>
|
||
<div className="text-[10px] text-[#B38B4D] font-medium">SAVE 150 KR • Pure veg delight</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Very subtle footer branding */}
|
||
<div className="relative z-10 py-2 text-center text-[10px] tracking-[2.5px] text-[#8A8478] border-t border-[#EDE6D9]/60 bg-[#F8F5F0] flex-none">
|
||
SHAHI KITCHEN • GOTHENBURG • FRESH EVERY DAY
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|