Footer Meshkee credit widget, fix carousel title row alignment/action button

- Footer bottom-right credit link now matches the badge used on other
  Meshkee storefronts (reference: safeteb.com) — logo + two-line label
  inside an animated corner-drawing border on hover, linking to
  https://meshkee.com. Added the shared credit-widget logo asset
  (public/images/meshkee-logo-white.png).
- SectionTitle: title/action row uses items-center instead of items-end
  (was visibly offset once a carousel title could wrap or carry a
  caption); the action button ("مشاهده همه" etc.) is now styled like
  the site's other primary buttons (filled red, white text, rounded)
  instead of a plain text link.
- Carousel English captions (SPECIAL SALE, TOP SELLING, FAVORITE SELL)
  moved back from a below-title "enSubtitle" line to the existing
  inline enTitle treatment, matching every other section heading's
  convention on the site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
amirhosein.ashourloo
2026-09-15 19:14:10 +03:30
co-authored by Claude Sonnet 5
parent ab1b07ff28
commit 7a78adc7f3
5 changed files with 64 additions and 36 deletions
+34 -8
View File
@@ -222,14 +222,17 @@ time the page renders, with zero code changes.
`SectionTitle`'s `enTitle` prop is optional for exactly this reason — these `SectionTitle`'s `enTitle` prop is optional for exactly this reason — these
dynamic groups only have one `title` string from the API, not a Persian/ dynamic groups only have one `title` string from the API, not a Persian/
English pair, so the inline English-caps subtitle just doesn't render for English pair. Each carousel still shows an English caption using that same
them. Instead, each carousel shows an English caption *under* the title `enTitle` slot (inline, beside the title — the same treatment as every
(`SectionTitle`'s separate `enSubtitle` prop, `opacity-50`, its own line) — other section heading on the site, e.g. BlogCarousel's "آخرین مطالب وبلاگ /
`StoreSpecialCarousels.englishLabelFromKey()` formats the group's `key` LATEST BLOG"; there was a brief detour through a separate "below the title,
(e.g. `top-selling` → "TOP SELLING") rather than inventing a translation of opacity-50, own line" `enSubtitle` prop, which the user then asked to match
`title`; there's no real localized-English field to translate from, and a the existing inline convention instead — don't reintroduce that below-title
made-up translation would be fabricated data. Skips rendering if the key variant). `StoreSpecialCarousels.englishLabelFromKey()` formats the group's
has no letters at all (fell back to a bare numeric id). `key` (e.g. `top-selling` → "TOP SELLING") rather than inventing a
translation of `title`; there's no real localized-English field to
translate from, and a made-up translation would be fabricated data. Skips
rendering if the key has no letters at all (fell back to a bare numeric id).
**Banner interleaving**: the two static-image banner sections (`TwoBanners`, **Banner interleaving**: the two static-image banner sections (`TwoBanners`,
`ThreeBanners`) are rendered *inside* `StoreSpecialCarousels`, one inserted `ThreeBanners`) are rendered *inside* `StoreSpecialCarousels`, one inserted
@@ -238,6 +241,14 @@ cycling through the two if there end up being more than two carousels. Don't
move them back out to `page.tsx` as fixed-position siblings — that only move them back out to `page.tsx` as fixed-position siblings — that only
works for a known, fixed carousel count, which this deliberately isn't. works for a known, fixed carousel count, which this deliberately isn't.
`SectionTitle`'s title/action row uses `items-center` (not `items-end`) —
needed once carousel titles could carry more content (the inline `enTitle`
caption) than a single line, so bottom-aligning the row left the action
button visibly offset from the title block. The `action` button itself is
styled like the site's other primary buttons (filled `bg-red`, white text,
`rounded-lg` — the same look as `ProductCard`'s "افزودن به سبد"), not a
plain text link.
**"مشاهده همه" (view all)**: each carousel's action button links to **"مشاهده همه" (view all)**: each carousel's action button links to
`/products?specialId={group.id}`. `store-specials` groups are admin-curated, `/products?specialId={group.id}`. `store-specials` groups are admin-curated,
already-complete lists (the endpoint has no pagination) — there's no already-complete lists (the endpoint has no pagination) — there's no
@@ -263,6 +274,21 @@ render a `<link rel="icon">` and browsers aren't guaranteed to prefer the
second one, so keeping both was silently serving the stale static icon. second one, so keeping both was silently serving the stale static icon.
Don't add `app/favicon.ico` (or `public/favicon.ico`) back. Don't add `app/favicon.ico` (or `public/favicon.ico`) back.
## Footer: Meshkee credit widget
The footer's bottom-right link ("Designed And Developed By Meshkee
E-Commerce Team" → `https://meshkee.com`) intentionally matches the credit
badge Meshkee's own storefronts use elsewhere (reference: safeteb.com) —
same markup shape (frame + logo + two-line label), same animated corner
border (four 1px lines that draw in on hover, staggered L→B→R→T, and
un-draw in reverse on mouse-leave via matching `transition-delay`s on the
resting state). `public/images/meshkee-logo-white.png` is that reference
site's own logo asset, pulled down and committed here since it's the
platform's shared credit-widget logo, not something unique to safeteb.com.
If this ever needs to change, re-derive the exact hover-timing values from
a live Meshkee site rather than guessing new ones — the staggered reverse
un-draw is the distinctive part, not just "a border that appears."
## Route map ## Route map
| Route | Notes | | Route | Notes |
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+23 -3
View File
@@ -116,10 +116,30 @@ export function Footer({
<a <a
href="https://meshkee.com" href="https://meshkee.com"
target="_blank" target="_blank"
rel="noreferrer" rel="noopener noreferrer"
className="font-display tracking-wide text-white/50 uppercase transition-colors hover:text-white" dir="ltr"
className="group relative inline-flex items-center gap-2.5 px-2.5 py-1.5 text-[#9c9c9c] transition-colors duration-300 hover:text-[#e0e0e0]"
> >
Powered by meshkee.com <span className="pointer-events-none absolute inset-0" aria-hidden="true">
<span className="absolute top-0 left-0 h-full w-px origin-top scale-y-0 bg-[#777] transition-transform duration-[280ms] delay-[420ms] group-hover:scale-y-100 group-hover:delay-0" />
<span className="absolute bottom-0 left-0 h-px w-full origin-left scale-x-0 bg-[#777] transition-transform duration-[280ms] delay-[280ms] group-hover:scale-x-100 group-hover:delay-[140ms]" />
<span className="absolute top-0 right-0 h-full w-px origin-bottom scale-y-0 bg-[#777] transition-transform duration-[280ms] delay-[140ms] group-hover:scale-y-100 group-hover:delay-[280ms]" />
<span className="absolute top-0 left-0 h-px w-full origin-right scale-x-0 bg-[#777] transition-transform duration-[280ms] delay-0 group-hover:scale-x-100 group-hover:delay-[420ms]" />
</span>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/images/meshkee-logo-white.png"
alt="Meshkee"
width={42}
height={42}
className="relative block h-[42px] w-auto shrink-0 object-contain opacity-70 transition-opacity duration-300 group-hover:opacity-100"
/>
<span className="relative flex min-h-[42px] flex-col justify-center leading-[1.25]">
<span className="text-[11px]">Designed And Developed By</span>
<span className="text-[13px] font-semibold text-[#cfcfcf] transition-colors duration-300 group-hover:text-white">
Meshkee E-Commerce Team
</span>
</span>
</a> </a>
</div> </div>
</div> </div>
@@ -11,8 +11,9 @@ import { productHref, type ProductGroup, type StaticImageWithUrl } from "@/lib/m
// localized English field to pull a real translation from, and inventing // localized English field to pull a real translation from, and inventing
// one would be fabricated data. `key` is real data and usually a readable // one would be fabricated data. `key` is real data and usually a readable
// English slug (e.g. "top-selling"), so it's what gets formatted into the // English slug (e.g. "top-selling"), so it's what gets formatted into the
// English caption under the title. Skip it if the key has no letters at // English caption beside the title (same inline treatment as every other
// all (e.g. it fell back to a bare numeric id). // section heading on the site, e.g. BlogCarousel's "LATEST BLOG"). Skip it
// if the key has no letters at all (e.g. it fell back to a bare numeric id).
function englishLabelFromKey(key: string): string | undefined { function englishLabelFromKey(key: string): string | undefined {
const label = key.replace(/[-_]+/g, " ").trim(); const label = key.replace(/[-_]+/g, " ").trim();
return /[a-zA-Z]/.test(label) ? label.toUpperCase() : undefined; return /[a-zA-Z]/.test(label) ? label.toUpperCase() : undefined;
@@ -58,7 +59,7 @@ export function StoreSpecialCarousels({
<div className="mx-auto max-w-7xl px-4"> <div className="mx-auto max-w-7xl px-4">
<SectionTitle <SectionTitle
title={group.title} title={group.title}
enSubtitle={englishLabelFromKey(group.key)} enTitle={englishLabelFromKey(group.key)}
action="مشاهده همه" action="مشاهده همه"
actionHref={`/products?specialId=${group.id}`} actionHref={`/products?specialId=${group.id}`}
/> />
+3 -22
View File
@@ -1,24 +1,14 @@
type SectionTitleProps = { type SectionTitleProps = {
title: string; title: string;
enTitle?: string; enTitle?: string;
// Shown on its own line under the title (not inline like enTitle), at 50%
// opacity — used for the homepage's dynamic store-specials carousels.
enSubtitle?: string;
subtitle?: string; subtitle?: string;
action?: string; action?: string;
actionHref?: string; actionHref?: string;
}; };
export function SectionTitle({ export function SectionTitle({ title, enTitle, subtitle, action, actionHref }: SectionTitleProps) {
title,
enTitle,
enSubtitle,
subtitle,
action,
actionHref,
}: SectionTitleProps) {
return ( return (
<div className="mb-6 flex items-end justify-between gap-4"> <div className="mb-6 flex items-center justify-between gap-4">
<div> <div>
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1"> <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<h2 className="text-xl font-bold text-red-dark md:text-2xl">{title}</h2> <h2 className="text-xl font-bold text-red-dark md:text-2xl">{title}</h2>
@@ -32,15 +22,6 @@ export function SectionTitle({
</span> </span>
)} )}
</div> </div>
{enSubtitle && (
<p
className="font-display mt-1 text-xs font-semibold tracking-wide text-foreground uppercase opacity-50 md:text-sm"
lang="en"
dir="ltr"
>
{enSubtitle}
</p>
)}
{subtitle && ( {subtitle && (
<p className="mt-1 text-sm text-muted">{subtitle}</p> <p className="mt-1 text-sm text-muted">{subtitle}</p>
)} )}
@@ -48,7 +29,7 @@ export function SectionTitle({
{action && actionHref && ( {action && actionHref && (
<a <a
href={actionHref} href={actionHref}
className="shrink-0 text-sm font-medium text-red transition-colors hover:text-red-dark" className="shrink-0 rounded-lg bg-red px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-dark"
> >
{action} ← {action} ←
</a> </a>