Polish homepage banners and add the Meshkee footer credit.

Keep slider and side banners from overlapping, restore rounded banner corners, and match the safeteb footer bar.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-17 11:19:03 +03:30
co-authored by Cursor
parent dfb791eb50
commit 03e377b6be
8 changed files with 219 additions and 39 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+155
View File
@@ -77,3 +77,158 @@ body {
.rich-content a { .rich-content a {
color: var(--color-primary); color: var(--color-primary);
} }
.footer-bottom-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.footer-bottom-site {
color: #9c9c9c;
font-size: 12px;
}
.meshkee-credit {
position: relative;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 6px 10px;
color: #9c9c9c;
text-decoration: none;
transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.meshkee-credit:hover {
color: #e0e0e0;
text-decoration: none;
}
.meshkee-credit__frame {
position: absolute;
inset: 0;
pointer-events: none;
}
.meshkee-credit__line {
position: absolute;
background: #777;
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.meshkee-credit__line--l {
top: 0;
left: 0;
width: 1px;
height: 100%;
transform: scaleY(0);
transform-origin: top;
transition-delay: 0s;
}
.meshkee-credit__line--b {
bottom: 0;
left: 0;
width: 100%;
height: 1px;
transform: scaleX(0);
transform-origin: left;
transition-delay: 0.14s;
}
.meshkee-credit__line--r {
top: 0;
right: 0;
width: 1px;
height: 100%;
transform: scaleY(0);
transform-origin: bottom;
transition-delay: 0.28s;
}
.meshkee-credit__line--t {
top: 0;
left: 0;
width: 100%;
height: 1px;
transform: scaleX(0);
transform-origin: right;
transition-delay: 0.42s;
}
.meshkee-credit:hover .meshkee-credit__line--l,
.meshkee-credit:hover .meshkee-credit__line--r {
transform: scaleY(1);
}
.meshkee-credit:hover .meshkee-credit__line--b,
.meshkee-credit:hover .meshkee-credit__line--t {
transform: scaleX(1);
}
.meshkee-credit:not(:hover) .meshkee-credit__line--l {
transition-delay: 0.42s;
}
.meshkee-credit:not(:hover) .meshkee-credit__line--b {
transition-delay: 0.28s;
}
.meshkee-credit:not(:hover) .meshkee-credit__line--r {
transition-delay: 0.14s;
}
.meshkee-credit:not(:hover) .meshkee-credit__line--t {
transition-delay: 0s;
}
.meshkee-credit__logo {
position: relative;
display: block;
flex-shrink: 0;
width: auto;
height: 42px;
aspect-ratio: 1 / 1;
object-fit: contain;
opacity: 0.7;
transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.meshkee-credit:hover .meshkee-credit__logo {
opacity: 1;
}
.meshkee-credit__text {
position: relative;
display: flex;
min-height: 42px;
flex-direction: column;
justify-content: center;
line-height: 1.25;
}
.meshkee-credit__label {
font-size: 11px;
}
.meshkee-credit__name {
color: #cfcfcf;
font-size: 13px;
font-weight: 600;
transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.meshkee-credit:hover .meshkee-credit__name {
color: #fff;
}
@media only screen and (max-width: 575px) {
.footer-bottom-bar {
flex-direction: column;
justify-content: center;
text-align: center;
}
}
+7 -3
View File
@@ -11,9 +11,13 @@ export function CategoryRow() {
href={category.href} href={category.href}
className="flex items-center gap-3 rounded-2xl bg-page px-3 py-4" className="flex items-center gap-3 rounded-2xl bg-page px-3 py-4"
> >
<span className="flex h-14 w-14 shrink-0 items-center justify-center rounded-full bg-white"> <Image
<Image src={category.icon} alt="" width={40} height={40} /> src={category.icon}
</span> alt=""
width={64}
height={64}
className="h-16 w-16 shrink-0 object-contain"
/>
<span> <span>
<span className="block font-semibold text-primary">{category.title}</span> <span className="block font-semibold text-primary">{category.title}</span>
<span className="mt-1 block text-xs text-muted">{category.subtitle}</span> <span className="mt-1 block text-xs text-muted">{category.subtitle}</span>
+30 -3
View File
@@ -4,7 +4,7 @@ import { consultants, navItems, site } from "@/data/site";
export function Footer() { export function Footer() {
return ( return (
<footer className="mt-8 bg-[#1f2a16] text-white"> <footer className="bg-[#1f2a16] text-white">
<div className="mx-auto grid max-w-7xl gap-10 px-4 py-12 md:grid-cols-4"> <div className="mx-auto grid max-w-7xl gap-10 px-4 py-12 md:grid-cols-4">
<div> <div>
<Image src="/images/logo-footer.png" alt={site.name} width={180} height={60} className="mb-4 h-12 w-auto brightness-0 invert" /> <Image src="/images/logo-footer.png" alt={site.name} width={180} height={60} className="mb-4 h-12 w-auto brightness-0 invert" />
@@ -61,8 +61,35 @@ export function Footer() {
</p> </p>
</div> </div>
</div> </div>
<div className="border-t border-white/10 py-4 text-center text-xs text-white/60"> <div className="border-t border-white/10">
طراحی و توسعه توسط تیم تجارت الکترونیک مشکی <div className="footer-bottom-bar mx-auto max-w-7xl px-4 py-3">
<p className="footer-bottom-site">{site.name}</p>
<a
href="https://meshkee.com"
target="_blank"
rel="noopener noreferrer"
dir="ltr"
className="meshkee-credit"
>
<span className="meshkee-credit__frame" aria-hidden="true">
<span className="meshkee-credit__line meshkee-credit__line--l" />
<span className="meshkee-credit__line meshkee-credit__line--t" />
<span className="meshkee-credit__line meshkee-credit__line--r" />
<span className="meshkee-credit__line meshkee-credit__line--b" />
</span>
<Image
src="/images/meshkee-logo-white.png"
alt="Meshkee"
width={42}
height={42}
className="meshkee-credit__logo"
/>
<span className="meshkee-credit__text">
<span className="meshkee-credit__label">Designed And Developed By</span>
<span className="meshkee-credit__name">Meshkee E-Commerce Team</span>
</span>
</a>
</div>
</div> </div>
</footer> </footer>
); );
+7 -6
View File
@@ -37,23 +37,24 @@ export function Hero({ slider, beside }: Props) {
const besideImages = padSlotImages(beside, 2).slice(0, 2); const besideImages = padSlotImages(beside, 2).slice(0, 2);
return ( return (
<section className="mx-auto grid max-w-7xl gap-4 px-4 py-6 lg:grid-cols-[2fr_1fr]"> <section className="mx-auto grid max-w-7xl gap-4 px-4 py-6 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
<h1 className="sr-only">{site.name}</h1> <h1 className="sr-only">{site.name}</h1>
<div className="min-h-[280px] min-w-0 sm:min-h-[360px]">
<HeroSlider <HeroSlider
images={slotImages(slider)} images={slotImages(slider)}
aspectRatio={slider?.aspectRatio}
fallback={sliderFallback} fallback={sliderFallback}
imageClassName="h-full min-h-[280px] w-full object-cover sm:min-h-[360px]" className="h-full w-full"
/> />
</div>
<div className="grid gap-4"> <div className="grid min-w-0 gap-4">
{besideImages.map((image, index) => ( {besideImages.map((image, index) => (
<SlotBanner <SlotBanner
key={image?.id ?? besideFallbacks[index].src} key={image?.id ?? besideFallbacks[index].src}
image={image} image={image}
fallback={besideFallbacks[index]} fallback={besideFallbacks[index]}
aspectRatio={beside?.aspectRatio} aspectRatio={beside?.aspectRatio ?? "74:41"}
imageClassName="h-full min-h-[160px] w-full object-cover" className="h-full min-h-0"
/> />
))} ))}
</div> </div>
+5 -10
View File
@@ -6,12 +6,11 @@ import type { StaticImage } from "@/lib/static-images";
type Props = { type Props = {
images: StaticImage[]; images: StaticImage[];
aspectRatio?: string | null;
fallback: SlotFallback; fallback: SlotFallback;
imageClassName: string; className?: string;
}; };
export function HeroSlider({ images, aspectRatio, fallback, imageClassName }: Props) { export function HeroSlider({ images, fallback, className = "" }: Props) {
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const slides = images.length > 0 ? images : [undefined]; const slides = images.length > 0 ? images : [undefined];
@@ -28,16 +27,14 @@ export function HeroSlider({ images, aspectRatio, fallback, imageClassName }: Pr
<SlotBanner <SlotBanner
image={slides[0]} image={slides[0]}
fallback={fallback} fallback={fallback}
aspectRatio={aspectRatio} className={className}
className="h-full"
imageClassName={imageClassName}
priority priority
/> />
); );
} }
return ( return (
<div className="relative h-full"> <div className={`relative min-w-0 overflow-hidden rounded-[18px] ${className}`}>
{slides.map((image, slideIndex) => ( {slides.map((image, slideIndex) => (
<div <div
key={image?.id ?? slideIndex} key={image?.id ?? slideIndex}
@@ -46,9 +43,7 @@ export function HeroSlider({ images, aspectRatio, fallback, imageClassName }: Pr
<SlotBanner <SlotBanner
image={image} image={image}
fallback={fallback} fallback={fallback}
aspectRatio={aspectRatio} className="h-full w-full"
className="h-full"
imageClassName={imageClassName}
priority={slideIndex === 0} priority={slideIndex === 0}
/> />
</div> </div>
+4 -7
View File
@@ -29,7 +29,7 @@ export function PromoBanners({ slot }: { slot?: StaticImageSlot }) {
const images = padSlotImages(slot, 2); const images = padSlotImages(slot, 2);
return ( return (
<section className="mx-auto grid max-w-7xl gap-4 px-4 py-4 md:grid-cols-2"> <section className="mx-auto grid max-w-7xl gap-4 px-4 py-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
{images.map((image, index) => { {images.map((image, index) => {
const fallback = rowFallbacks[index] ?? rowFallbacks[1]; const fallback = rowFallbacks[index] ?? rowFallbacks[1];
return ( return (
@@ -37,8 +37,7 @@ export function PromoBanners({ slot }: { slot?: StaticImageSlot }) {
key={image?.id ?? `${fallback.src}-${index}`} key={image?.id ?? `${fallback.src}-${index}`}
image={image} image={image}
fallback={fallback} fallback={fallback}
aspectRatio={slot?.aspectRatio} aspectRatio={slot?.aspectRatio ?? "569:264"}
imageClassName="h-48 w-full object-cover sm:h-56"
/> />
); );
})} })}
@@ -61,8 +60,7 @@ export function DiscountBanner({ slot }: { slot?: StaticImageSlot }) {
width: 1168, width: 1168,
height: 264, height: 264,
}} }}
aspectRatio={slot?.aspectRatio} aspectRatio={slot?.aspectRatio ?? "146:33"}
imageClassName="h-40 w-full object-cover sm:h-48"
> >
{(image.titleFa || image.titleEn || image.title || image.subtext) && ( {(image.titleFa || image.titleEn || image.title || image.subtext) && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-end px-6 sm:px-10"> <div className="pointer-events-none absolute inset-0 flex items-center justify-end px-6 sm:px-10">
@@ -92,8 +90,7 @@ export function DiscountBanner({ slot }: { slot?: StaticImageSlot }) {
width: 1168, width: 1168,
height: 264, height: 264,
}} }}
aspectRatio={slot?.aspectRatio} aspectRatio={slot?.aspectRatio ?? "146:33"}
imageClassName="h-40 w-full object-cover sm:h-48"
> >
<div className="absolute inset-0 flex items-center justify-between px-6 sm:px-10"> <div className="absolute inset-0 flex items-center justify-between px-6 sm:px-10">
<Image <Image
+7 -6
View File
@@ -27,13 +27,14 @@ export function SlotBanner({
fallback, fallback,
aspectRatio, aspectRatio,
className = "", className = "",
imageClassName, imageClassName = "",
priority, priority,
children, children,
}: Props) { }: Props) {
const alt = imageAlt(image, fallback.alt); const alt = imageAlt(image, fallback.alt);
const mediaClassName = `block h-full w-full object-cover ${imageClassName}`;
const media = image?.url ? ( const media = image?.url ? (
<img src={image.url} alt={alt} className={imageClassName} /> <img src={image.url} alt={alt} className={mediaClassName} />
) : ( ) : (
<Image <Image
src={fallback.src} src={fallback.src}
@@ -41,18 +42,18 @@ export function SlotBanner({
width={fallback.width} width={fallback.width}
height={fallback.height} height={fallback.height}
priority={priority} priority={priority}
className={imageClassName} className={mediaClassName}
/> />
); );
const href = image?.url ? image.linkUrl || undefined : fallback.href; const href = image?.url ? image.linkUrl || undefined : fallback.href;
const wrapped = href ? ( const wrapped = href ? (
image?.url ? ( image?.url ? (
<a href={href} className="block h-full"> <a href={href} className="block h-full w-full">
{media} {media}
</a> </a>
) : ( ) : (
<Link href={href} className="block h-full"> <Link href={href} className="block h-full w-full">
{media} {media}
</Link> </Link>
) )
@@ -62,7 +63,7 @@ export function SlotBanner({
return ( return (
<div <div
className={`relative overflow-hidden rounded-[18px] ${className}`} className={`relative min-w-0 overflow-hidden rounded-[18px] ${className}`}
style={aspectRatioStyle(aspectRatio)} style={aspectRatioStyle(aspectRatio)}
> >
{wrapped} {wrapped}