Wire homepage banners to Meshkee static-image slots.

Publish a slot catalog so the dashboard can import keys, and keep local placeholders when a slot is empty.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-25 17:54:10 +03:30
co-authored by Cursor
parent 46e4835360
commit 49b83223f1
12 changed files with 766 additions and 147 deletions
+92 -28
View File
@@ -1,47 +1,111 @@
import Image from "next/image";
import Link from "next/link";
import { SlotImage } from "@/components/SlotImage";
import { banners3, banners4 } from "@/lib/data";
import type { StaticImageWithUrl } from "@/lib/static-images";
import { slotAlt, slotAspectStyle } from "@/lib/static-images";
import { Media } from "./Media";
export function BannerGrid4() {
const banner4Overlay = {
background: "linear-gradient(to top, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.55) 45%, transparent 100%)",
};
type BannerSlotProps = {
images?: StaticImageWithUrl[];
aspectRatio?: string;
};
export function BannerGrid4({ images = [], aspectRatio }: BannerSlotProps) {
return (
<section className="container-site mt-12">
<div className="grid grid-cols-2 gap-3 md:gap-4">
{banners4.map((banner) => (
<Link key={banner.href} href={banner.href} className="relative block overflow-hidden rounded-2xl">
<div className="relative aspect-[21/9] min-h-[140px]">
<Image src={banner.image} alt="" fill sizes="50vw" className="object-cover object-center" />
</div>
<div
className="pointer-events-none absolute inset-x-0 bottom-0 h-[70%]"
style={{
background:
"linear-gradient(to top, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.55) 45%, transparent 100%)",
}}
/>
<div className="absolute inset-x-0 bottom-3 md:bottom-4 px-3 text-center text-white">
<p className="text-base md:text-xl font-extrabold leading-none">{banner.title}</p>
<p dir="ltr" className="mt-0.5 font-carter text-[9px] md:text-[10px] tracking-[0.4em] text-brand uppercase">
{banner.english}
</p>
<p className="mt-0.5 text-sm md:text-base leading-tight text-white/90">{banner.subtitle}</p>
</div>
</Link>
))}
{images.length > 0
? images.map((image) => {
const title = image.titleFa?.trim();
const english = image.titleEn?.trim();
const subtitle = image.subtext?.trim();
const hasCopy = Boolean(title || english || subtitle);
const frame = (
<div
className="relative overflow-hidden rounded-2xl"
style={slotAspectStyle(aspectRatio, "21:9")}
>
<SlotImage image={{ ...image, linkUrl: null }} fill alt={slotAlt(image)} />
{hasCopy ? (
<>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-[70%]" style={banner4Overlay} />
<div className="pointer-events-none absolute inset-x-0 bottom-3 md:bottom-4 px-3 text-center text-white">
{title ? (
<p className="text-base md:text-xl font-extrabold leading-none">{title}</p>
) : null}
{english ? (
<p
dir="ltr"
className="mt-0.5 font-carter text-[9px] md:text-[10px] tracking-[0.4em] text-brand uppercase"
>
{english}
</p>
) : null}
{subtitle ? (
<p className="mt-0.5 text-sm md:text-base leading-tight text-white/90">{subtitle}</p>
) : null}
</div>
</>
) : null}
</div>
);
return image.linkUrl ? (
<a key={image.id} href={image.linkUrl} className="block">
{frame}
</a>
) : (
<div key={image.id}>{frame}</div>
);
})
: banners4.map((banner) => (
<Link key={banner.href} href={banner.href} className="relative block overflow-hidden rounded-2xl">
<div className="relative aspect-[21/9] min-h-[140px]">
<Image src={banner.image} alt={banner.title} fill sizes="50vw" className="object-cover object-center" />
</div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-[70%]" style={banner4Overlay} />
<div className="absolute inset-x-0 bottom-3 md:bottom-4 px-3 text-center text-white">
<p className="text-base md:text-xl font-extrabold leading-none">{banner.title}</p>
<p
dir="ltr"
className="mt-0.5 font-carter text-[9px] md:text-[10px] tracking-[0.4em] text-brand uppercase"
>
{banner.english}
</p>
<p className="mt-0.5 text-sm md:text-base leading-tight text-white/90">{banner.subtitle}</p>
</div>
</Link>
))}
</div>
</section>
);
}
export function BannerRow3() {
export function BannerRow3({ images = [], aspectRatio }: BannerSlotProps) {
return (
<section className="container-site mt-12">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 md:gap-4">
{banners3.map((banner) => (
<Link key={banner.href} href={banner.href} className="overflow-hidden rounded-2xl">
<Media src={banner.image} alt="" className="aspect-[2.4/1]" sizes="33vw" />
</Link>
))}
{images.length > 0
? images.map((image) => (
<div key={image.id} className="overflow-hidden rounded-2xl">
<SlotImage
image={image}
alt={slotAlt(image)}
imgClassName="h-auto w-full object-cover"
aspectRatio={aspectRatio}
fallbackAspect="12:5"
/>
</div>
))
: banners3.map((banner) => (
<Link key={banner.href} href={banner.href} className="overflow-hidden rounded-2xl">
<Media src={banner.image} alt={banner.alt} className="aspect-[2.4/1]" sizes="33vw" />
</Link>
))}
</div>
</section>
);