Show real titleFa captions, 3x logo size, fix category hover border
- CategoryCarousel: caption under each circle now comes from the slot image's own titleFa (as the business typed it) instead of the resolved category's API name - identity resolution via linkUrl slug is still used for the href, just not for the visible label - Replace the hover ring (box-shadow) with a real 1px border - the ring rendered oddly against Swiper's transformed slide track, looking like it "leaked" into the row above; a border participates in normal box layout and doesn't have that problem (border-box sizing means zero layout shift) - Header/footer logo: h-9 (36px) -> h-[108px], 3x on request; header row height bumped to fit. Note: the currently-uploaded (wrong-business) logo has a lot of baked-in padding, so it still reads small even at 3x - that's the asset, not the sizing - Re-verified the static-image-slot wiring end to end against the original spec (url->img, linkUrl->href, aspectRatio from API, placeholder on empty/fail) - all still holds Also add a Deployment section to AGENTS.md: this repo's whole point is CI/CD from origin/main, and local commits sitting unpushed (as 3 commits were, until now) look indistinguishable from unfixed bugs to whoever is testing the deployed site. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2249a07dee
commit
0ffbd953b3
@@ -58,6 +58,10 @@ Fonts: IRANYekan (fa, local `public/fonts`) + Montserrat (latin, next/font).
|
|||||||
mark, not موبایل معلم) — the wiring is correct and will pick up the right
|
mark, not موبایل معلم) — the wiring is correct and will pick up the right
|
||||||
asset automatically the moment it's replaced in the dashboard; don't
|
asset automatically the moment it's replaced in the dashboard; don't
|
||||||
"fix" this by hardcoding the local fallback again.
|
"fix" this by hardcoding the local fallback again.
|
||||||
|
- Header/footer logo renders at `h-[108px]` (3x the original `h-9`/36px, on
|
||||||
|
request). If the wrong logo above still looks small at that size, it's
|
||||||
|
because that specific PNG has a lot of baked-in transparent padding around
|
||||||
|
the mark — `object-contain` can't fix that, only the replacement asset can.
|
||||||
- Contact data (phone, mobile, branch addresses, Instagram/Telegram) in
|
- Contact data (phone, mobile, branch addresses, Instagram/Telegram) in
|
||||||
`src/data/site.ts` is real, pulled from the live theme's footer — `GET
|
`src/data/site.ts` is real, pulled from the live theme's footer — `GET
|
||||||
/website/business-info` still returns empty `phoneNumbers`/`addresses`/
|
/website/business-info` still returns empty `phoneNumbers`/`addresses`/
|
||||||
@@ -142,10 +146,21 @@ business's upload order lines up with this site's category sort order. It
|
|||||||
doesn't, and there's no contract that it ever would — live data showed a
|
doesn't, and there's no contract that it ever would — live data showed a
|
||||||
laptop photo (`linkUrl` correctly said `?category=laptop`) landing on
|
laptop photo (`linkUrl` correctly said `?category=laptop`) landing on
|
||||||
"موبایل" and "لوازم برقی" appearing with no photo uploaded for it at all,
|
"موبایل" and "لوازم برقی" appearing with no photo uploaded for it at all,
|
||||||
purely because of where it fell in the array. `titleFa` isn't reliable
|
purely because of where it fell in the array. The visible caption under each
|
||||||
either — the same laptop image was titled "تبلت" (stale/copy-pasted text),
|
circle is that image's own `titleFa`, taken as typed — even on the one
|
||||||
contradicting its own `linkUrl`. The `linkUrl` query param was the one field
|
sample where it disagreed with the photo (that same laptop image was titled
|
||||||
that matched the actual photo content every time it was checked.
|
"تبلت"), it's still the business's own editorial text for the tile, and the
|
||||||
|
`href` (built from the `linkUrl` slug, resolved to this site's own canonical
|
||||||
|
`/products/category/{id}/{slug}` rather than linking off-site to the literal
|
||||||
|
`linkUrl`) is what actually has to be correct for navigation to work.
|
||||||
|
|
||||||
|
**Category circle hover:** use a real `border` (1px, transparent → `border-brand`
|
||||||
|
on `group-hover`), not a `ring`/box-shadow. A ring rendered inside a Swiper
|
||||||
|
slide painted like it was "leaking" into the row above — box-shadow doesn't
|
||||||
|
participate in normal layout the way a border does, and interacts oddly with
|
||||||
|
Swiper's transformed `.swiper-wrapper` track. `border` with this site's
|
||||||
|
global `box-sizing: border-box` reset adds zero layout shift, so it's the
|
||||||
|
safer default for any future hover-outline effect inside a carousel.
|
||||||
|
|
||||||
Homepage banner placement (mirrors the logilook.com reference given for this
|
Homepage banner placement (mirrors the logilook.com reference given for this
|
||||||
layout): hero → categories → hot deals → **three-banner-bg** → موبایل
|
layout): hero → categories → hot deals → **three-banner-bg** → موبایل
|
||||||
@@ -222,3 +237,14 @@ npm run dev # localhost:3000
|
|||||||
npm run build
|
npm run build
|
||||||
node scripts/gen-category-placeholders.mjs # refresh category thumb placeholders
|
node scripts/gen-category-placeholders.mjs # refresh category thumb placeholders
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
`origin` is `gitea.meshkee.com/meshkee-websites/moalem-shop.git`, `main` branch.
|
||||||
|
Whatever serves the business's preview/live URL deploys from `origin/main` —
|
||||||
|
**committing locally is not enough**, push too. A whole session's worth of
|
||||||
|
work (2026-09-06: about/contact/installments pages, the category-slot
|
||||||
|
identity fix, live logo/favicon wiring) once sat 3 commits ahead of
|
||||||
|
`origin/main` unpushed, and the business reported bugs that were already
|
||||||
|
fixed locally — they were looking at the old deployed code. Check
|
||||||
|
`git status` for "ahead of origin" before telling anyone a fix is live.
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import type { CategoryNode, StaticImageSlot } from "@/lib/types";
|
|||||||
|
|
||||||
// Fully slot-driven: each image in the "categories" slot carries its own
|
// Fully slot-driven: each image in the "categories" slot carries its own
|
||||||
// linkUrl (e.g. "...?category=mobile"), whose `category` param is the real
|
// linkUrl (e.g. "...?category=mobile"), whose `category` param is the real
|
||||||
// DB slug — that's what identifies which category a photo is for, not the
|
// DB slug — that's what identifies which category a photo is FOR (used to
|
||||||
// image's position in the list or its (business-typed, sometimes wrong)
|
// build the real, in-site link target). The visible caption is the
|
||||||
// titleFa. A category the business hasn't uploaded a photo for simply isn't
|
// business's own titleFa for that image, taken as typed — a category the
|
||||||
// shown here — no placeholder, per the "only what's in the panel" brief.
|
// business hasn't uploaded a photo for simply isn't shown here, no
|
||||||
|
// placeholder, per the "only what's in the panel" brief.
|
||||||
// See AGENTS.md "Static-image slots" for the mismatch this replaced.
|
// See AGENTS.md "Static-image slots" for the mismatch this replaced.
|
||||||
export default function CategoryCarousel({
|
export default function CategoryCarousel({
|
||||||
roots,
|
roots,
|
||||||
@@ -29,9 +30,10 @@ export default function CategoryCarousel({
|
|||||||
const catSlug = categorySlugFromLinkUrl(img.linkUrl);
|
const catSlug = categorySlugFromLinkUrl(img.linkUrl);
|
||||||
const cat = catSlug ? findNodeBySlug(roots, catSlug) : undefined;
|
const cat = catSlug ? findNodeBySlug(roots, catSlug) : undefined;
|
||||||
if (!cat) return null;
|
if (!cat) return null;
|
||||||
return { key: img.id, art: img.url, cat };
|
const label = img.titleFa || img.titleEn || cat.nameFa || cat.name;
|
||||||
|
return { key: img.id, art: img.url, label, href: categoryHref(cat.id, cat.nameFa, cat.name) };
|
||||||
})
|
})
|
||||||
.filter((x): x is { key: string; art: string; cat: CategoryNode } => Boolean(x));
|
.filter((x): x is { key: string; art: string; label: string; href: string } => Boolean(x));
|
||||||
|
|
||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
@@ -49,21 +51,29 @@ export default function CategoryCarousel({
|
|||||||
1024: { slidesPerView: 7 },
|
1024: { slidesPerView: 7 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{items.map(({ key, art, cat }) => {
|
{items.map(({ key, art, label, href }) => (
|
||||||
const label = cat.nameFa || cat.name;
|
<SwiperSlide key={key}>
|
||||||
return (
|
<Link href={href} className="group flex flex-col items-center gap-2">
|
||||||
<SwiperSlide key={key}>
|
{/* A real border, not ring/box-shadow — box-shadow rings can
|
||||||
<Link href={categoryHref(cat.id, cat.nameFa, cat.name)} className="group flex flex-col items-center gap-2">
|
paint oddly against Swiper's transformed slide track
|
||||||
<span className="relative size-[104px] overflow-hidden rounded-full bg-surface transition group-hover:bg-brand-soft sm:size-[124px] lg:size-[140px]">
|
(looked like the border "leaking" into the row above).
|
||||||
<Image src={art} alt={label} fill sizes="140px" className="object-contain p-3" />
|
border-box sizing (this site's global reset) means the
|
||||||
</span>
|
1px border adds no layout shift. */}
|
||||||
<span className="text-center text-[13.5px] text-ink transition group-hover:text-brand">
|
<span className="relative size-[104px] overflow-hidden rounded-full border border-transparent bg-surface transition-all duration-300 group-hover:border-brand group-hover:shadow-md sm:size-[124px] lg:size-[140px]">
|
||||||
{label}
|
<Image
|
||||||
</span>
|
src={art}
|
||||||
</Link>
|
alt={label}
|
||||||
</SwiperSlide>
|
fill
|
||||||
);
|
sizes="140px"
|
||||||
})}
|
className="rounded-full object-contain p-3 transition-transform duration-300 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="text-center text-[13.5px] text-ink transition group-hover:text-brand">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,9 +17,15 @@ export default function Footer({
|
|||||||
<footer className="mt-14 border-t border-line bg-surface">
|
<footer className="mt-14 border-t border-line bg-surface">
|
||||||
<div className="boxed grid gap-8 py-10 md:grid-cols-4">
|
<div className="boxed grid gap-8 py-10 md:grid-cols-4">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
{/* h-9 w-auto so a square dashboard logo and the wide fallback
|
{/* h-[108px] w-auto so a square dashboard logo and the wide
|
||||||
wordmark both render undistorted — see Header.tsx. */}
|
fallback wordmark both render undistorted — see Header.tsx. */}
|
||||||
<Image src={logoUrl} alt={SITE_NAME} width={140} height={40} className="h-9 w-auto object-contain" />
|
<Image
|
||||||
|
src={logoUrl}
|
||||||
|
alt={SITE_NAME}
|
||||||
|
width={280}
|
||||||
|
height={108}
|
||||||
|
className="h-[108px] w-auto object-contain"
|
||||||
|
/>
|
||||||
<p className="mt-3 max-w-md text-[13px] leading-7 text-body">{SITE_TAGLINE}</p>
|
<p className="mt-3 max-w-md text-[13px] leading-7 text-body">{SITE_TAGLINE}</p>
|
||||||
<a
|
<a
|
||||||
href={PHONE_HREF}
|
href={PHONE_HREF}
|
||||||
|
|||||||
@@ -28,12 +28,21 @@ export default function Header({
|
|||||||
<header className="sticky top-0 z-40 w-full border-b border-line bg-white">
|
<header className="sticky top-0 z-40 w-full border-b border-line bg-white">
|
||||||
<div className="boxed">
|
<div className="boxed">
|
||||||
{/* Mobile keeps the header down to the search box alone — account,
|
{/* Mobile keeps the header down to the search box alone — account,
|
||||||
cart and browsing live in the fixed bottom bar instead. */}
|
cart and browsing live in the fixed bottom bar instead. The row
|
||||||
<div className="flex h-[70px] items-center gap-4">
|
is taller on lg so a prominent 108px logo (3x the old 36px) has
|
||||||
|
room without cramming the search box/buttons beside it. */}
|
||||||
|
<div className="flex h-[70px] items-center gap-4 lg:h-32">
|
||||||
<Link href="/" className="hidden shrink-0 lg:block" aria-label={SITE_NAME}>
|
<Link href="/" className="hidden shrink-0 lg:block" aria-label={SITE_NAME}>
|
||||||
{/* h-9 w-auto (not a fixed box) so a square dashboard logo mark
|
{/* h-[108px] w-auto (not a fixed box) so a square dashboard logo
|
||||||
and the wide fallback wordmark both render undistorted. */}
|
mark and the wide fallback wordmark both render undistorted. */}
|
||||||
<Image src={logoUrl} alt={SITE_NAME} width={140} height={40} className="h-9 w-auto object-contain" priority />
|
<Image
|
||||||
|
src={logoUrl}
|
||||||
|
alt={SITE_NAME}
|
||||||
|
width={280}
|
||||||
|
height={108}
|
||||||
|
className="h-[108px] w-auto object-contain"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user