Compare commits

...
7 Commits
Author SHA1 Message Date
amirhosein.ashourlooandClaude Sonnet 5 69c54d3b17 Set footer top border color to #dddddd
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 19:41:15 +03:30
amirhosein.ashourlooandClaude Sonnet 5 c4411ebc28 Set mobile/non-desktop header logo height to 65px
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 19:40:17 +03:30
amirhosein.ashourlooandClaude Sonnet 5 69d17a6f8f Change footer background to #f1f1f1
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 19:39:04 +03:30
amirhosein.ashourlooandClaude Sonnet 5 d292bfc74c Resize header login/cart icon buttons to 40px, cart bg to #f1f1f1
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 19:35:15 +03:30
amirhosein.ashourlooandClaude Sonnet 5 d090f57540 Redesign header icons/account-cart chip, light footer, fix sticky-footer gap
- New shared SVG icon set (src/components/ui/icons.tsx): Menu, User,
  Cart, Close, ChevronLeft — replaces emoji (☰ 👤 🛒 ✕) across the
  header, and the literal "←" text arrow on every "view more"-style
  link (SectionTitle's action, the mega-menu link, BlogCarousel's
  "ادامه مطلب").
- Header desktop account/cart area redesigned as a borderless chip:
  cart icon button (right) + divider + account icon-badge/two-line
  "حساب کاربری / ورود/ثبت‌نام" (left), matching a supplied reference.
  Got the RTL DOM order backwards on the first pass (cart needs to be
  first in DOM to render rightmost) and corrected it.
- Header logo: -10px (was already doubled from an earlier request).
- Footer: background is now bg-[#9a21291a] (exact hex given, not a
  token substitute) with black text throughout instead of solid
  dark-red/white; footer logo doubled; the meshkee-credit logo gets
  brightness-0 so the white source asset stays visible on the new
  light background.
- Fixed a real layout bug: <main> had no flex-1, so on any page
  shorter than the viewport the footer stuck to the content instead of
  the bottom of the screen, leaving a gap of bare background beneath
  it. Standard sticky-footer flex fix.
- CONTEXT.md: documented the icon set, the account/cart chip's RTL DOM
  ordering (to stop this regressing again), and the footer's new
  color scheme + the flex-1 fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 19:31:00 +03:30
amirhosein.ashourlooandClaude Sonnet 5 7a78adc7f3 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>
2026-09-15 19:14:10 +03:30
amirhosein.ashourlooandClaude Sonnet 5 ab1b07ff28 Footer credit line links to meshkee.com
Replace the static "Designed for Sanihome" text with a real link
("Powered by meshkee.com" -> https://meshkee.com).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 18:58:56 +03:30
10 changed files with 246 additions and 74 deletions
+76 -8
View File
@@ -180,6 +180,28 @@ persistent memory as "for all the user's sites", not just this one)
in the array — first DOM child renders rightmost under `dir="rtl"`).
- Category tile images: 110px, no border, no border-radius
(`FeaturedCategories.tsx`).
- **No emoji as icons** — `src/components/ui/icons.tsx` has the shared
light/outline SVG set (`MenuIcon`, `UserIcon`, `CartIcon`, `CloseIcon`,
`ChevronLeftIcon`), 24x24 viewBox, `stroke="currentColor"`. Use these
instead of characters like "☰"/"👤"/"🛒"/"✕". Every "view more"-style
link/button (`SectionTitle`'s `action`, the mega-menu's "مشاهده همه",
`BlogCarousel`'s "ادامه مطلب") uses `ChevronLeftIcon`, not a literal "←"
character — keep new ones consistent with that.
- **Header account/cart cluster** (`Header.tsx`, desktop only —
`hidden ... sm:flex`): a borderless `bg-surface` chip holding, in this
exact DOM order, `CartButton`, a divider, then the login link. That DOM
order is deliberate and easy to get backwards: under `dir="rtl"` the
first DOM child renders rightmost, so cart-first-in-DOM is what actually
puts the cart icon on the visual **right** and the login block on the
**left** (this was built backwards once and corrected). Same rule inside
the login link itself — the icon badge comes before the two-line text in
DOM so the icon renders right of the text. Neither `CartButton` nor this
wrapper has a border (removed on request — don't reintroduce one).
`CartButton` is a fixed 44px (`h-11 w-11`) icon-only square everywhere it
renders (also stands alone, un-wrapped, below `sm`); its unread-count
badge sits at `-top-1.5 -right-1.5`, not the product-card cart badge's
`-left-1.5` — don't copy that positioning between the two, they're
intentionally different corners for different-looking buttons.
## Static-image slots
@@ -222,14 +244,17 @@ time the page renders, with zero code changes.
`SectionTitle`'s `enTitle` prop is optional for exactly this reason — these
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
them. Instead, each carousel shows an English caption *under* the title
(`SectionTitle`'s separate `enSubtitle` prop, `opacity-50`, its own line) —
`StoreSpecialCarousels.englishLabelFromKey()` formats the group's `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).
English pair. Each carousel still shows an English caption using that same
`enTitle` slot (inline, beside the title — the same treatment as every
other section heading on the site, e.g. BlogCarousel's "آخرین مطالب وبلاگ /
LATEST BLOG"; there was a brief detour through a separate "below the title,
opacity-50, own line" `enSubtitle` prop, which the user then asked to match
the existing inline convention instead — don't reintroduce that below-title
variant). `StoreSpecialCarousels.englishLabelFromKey()` formats the group's
`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`,
`ThreeBanners`) are rendered *inside* `StoreSpecialCarousels`, one inserted
@@ -238,6 +263,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
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
`/products?specialId={group.id}`. `store-specials` groups are admin-curated,
already-complete lists (the endpoint has no pagination) — there's no
@@ -263,6 +296,41 @@ 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.
Don't add `app/favicon.ico` (or `public/favicon.ico`) back.
## Footer: light background, black text, sticky to viewport bottom
The footer is `bg-[#f1f1f1]` (exact hex given by the user, same neutral
gray as `CartButton`'s background — not swapped for a design-token color;
an earlier version used a red-tinted `#9a21291a`, since replaced) with
black text throughout (`text-black`, `/70`, `/50`, `/10`, `/20` opacity
steps) — it used to be a solid dark-red block with white text; don't revert
to that. The footer logo (`logoDarkUrl ?? logoUrl`) is `h-[72px]`, doubled
from its original size on request. Because `public/images/meshkee-logo-
white.png` (the credit-widget logo below) is a solid-white asset, it gets
`brightness-0` on this light background to render as a black silhouette
instead of invisible white-on-near-white — the same `opacity-60 →
group-hover:opacity-100` fade still applies on top of that.
`<main>` has `flex-1` in `layout.tsx` (`body` is `flex flex-col`) so the
footer sits flush at the bottom of the viewport on short pages instead of
leaving a gap of bare `background` color beneath it — this is the standard
sticky-footer flex pattern. Don't remove `flex-1` from `main` without
re-checking a short page (e.g. `/contact`) for that gap coming 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 | Notes |
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+1 -1
View File
@@ -53,7 +53,7 @@ export default async function RootLayout({ children }: LayoutProps<"/">) {
<Providers>
<TopBar />
<Header categoryTree={categoryTree} categories={categories} logoUrl={businessInfo?.logoUrl} />
<main>{children}</main>
<main className="flex-1">{children}</main>
<Footer businessInfo={businessInfo} categories={categories} />
</Providers>
</body>
+5 -4
View File
@@ -1,6 +1,7 @@
"use client";
import { useCart } from "@/context/CartContext";
import { CartIcon } from "@/components/ui/icons";
export function CartButton() {
const { itemCount, openPopup } = useCart();
@@ -9,12 +10,12 @@ export function CartButton() {
<button
type="button"
onClick={openPopup}
className="relative flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 transition-colors hover:border-red hover:text-red"
aria-label="سبد خرید"
className="relative flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[#f1f1f1] text-foreground transition-colors hover:text-red"
>
<span aria-hidden>🛒</span>
<span className="hidden sm:inline">سبد خرید</span>
<CartIcon className="h-5 w-5" />
{itemCount > 0 && (
<span className="absolute -top-1.5 -left-1.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red px-1 text-[10px] font-bold text-white">
<span className="absolute -top-1.5 -right-1.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red px-1 text-[10px] font-bold text-white">
{itemCount}
</span>
)}
+4 -2
View File
@@ -2,6 +2,7 @@ import { Placeholder } from "@/components/ui/Placeholder";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { Carousel } from "@/components/ui/Carousel";
import { EmptyState } from "@/components/ui/EmptyState";
import { ChevronLeftIcon } from "@/components/ui/icons";
import type { BlogSummary } from "@/lib/meshkee";
function formatDate(iso?: string) {
@@ -50,8 +51,9 @@ export function BlogCarousel({ posts }: { posts: BlogSummary[] }) {
{post.abstract}
</p>
)}
<span className="mt-3 block text-xs font-medium text-red">
ادامه مطلب ←
<span className="mt-3 flex items-center gap-1 text-xs font-medium text-red">
ادامه مطلب
<ChevronLeftIcon className="h-3.5 w-3.5" />
</span>
</div>
</a>
+40 -15
View File
@@ -27,7 +27,7 @@ export function Footer({
);
return (
<footer className="border-t border-red-dark/20 bg-red-dark text-white">
<footer className="border-t border-[#dddddd] bg-[#f1f1f1] text-black">
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="grid gap-8 md:grid-cols-4">
<div>
@@ -37,7 +37,7 @@ export function Footer({
<img
src={businessInfo.logoDarkUrl ?? businessInfo.logoUrl ?? undefined}
alt={businessInfo.nameFa || "سانی‌هوم"}
className="h-9 w-auto object-contain"
className="h-[72px] w-auto object-contain"
/>
) : (
<span className="font-display text-lg font-bold tracking-tight">
@@ -46,16 +46,16 @@ export function Footer({
)}
</div>
{businessInfo?.about && (
<p className="text-sm leading-relaxed text-white/70">{businessInfo.about}</p>
<p className="text-sm leading-relaxed text-black/70">{businessInfo.about}</p>
)}
</div>
<div>
<h4 className="mb-3 text-sm font-bold">دسترسی سریع</h4>
<ul className="space-y-2 text-sm text-white/70">
<ul className="space-y-2 text-sm text-black/70">
{quickLinks.map((link) => (
<li key={link}>
<a href="#" className="transition-colors hover:text-white">
<a href="#" className="transition-colors hover:text-black">
{link}
</a>
</li>
@@ -66,12 +66,12 @@ export function Footer({
<div>
<h4 className="mb-3 text-sm font-bold">دسته‌بندی‌ها</h4>
{categories.length === 0 ? (
<p className="text-sm text-white/50">هنوز دسته‌بندی‌ای ثبت نشده است.</p>
<p className="text-sm text-black/50">هنوز دسته‌بندی‌ای ثبت نشده است.</p>
) : (
<ul className="space-y-2 text-sm text-white/70">
<ul className="space-y-2 text-sm text-black/70">
{categories.slice(0, 5).map((cat) => (
<li key={cat.id}>
<a href="#" className="transition-colors hover:text-white">
<a href="#" className="transition-colors hover:text-black">
{cat.name}
</a>
</li>
@@ -83,7 +83,7 @@ export function Footer({
<div>
<h4 className="mb-3 text-sm font-bold">تماس با ما</h4>
{hasContactInfo ? (
<ul className="space-y-2 text-sm text-white/70">
<ul className="space-y-2 text-sm text-black/70">
{businessInfo?.addresses.map((address) => <li key={address}>{address}</li>)}
{businessInfo?.phoneNumbers.map((phone) => (
<li key={phone}>تلفن: {phone}</li>
@@ -91,7 +91,7 @@ export function Footer({
{businessInfo?.emails.map((email) => <li key={email}>ایمیل: {email}</li>)}
</ul>
) : (
<p className="text-sm text-white/50">هنوز اطلاعات تماس ثبت نشده است.</p>
<p className="text-sm text-black/50">هنوز اطلاعات تماس ثبت نشده است.</p>
)}
{socialEntries.length > 0 && (
<div className="mt-4 flex gap-3">
@@ -101,7 +101,7 @@ export function Footer({
href={url}
target="_blank"
rel="noreferrer"
className="font-display flex h-8 w-8 items-center justify-center rounded-full border border-white/20 text-[10px] font-bold transition-colors hover:border-white hover:bg-white/10"
className="font-display flex h-8 w-8 items-center justify-center rounded-full border border-black/20 text-[10px] font-bold transition-colors hover:border-black hover:bg-black/5"
>
{socialLabels[key] ?? key.slice(0, 2).toUpperCase()}
</a>
@@ -111,11 +111,36 @@ export function Footer({
</div>
</div>
<div className="mt-10 flex flex-col items-center justify-between gap-3 border-t border-white/10 pt-6 text-xs text-white/50 md:flex-row">
<div className="mt-10 flex flex-col items-center justify-between gap-3 border-t border-black/10 pt-6 text-xs text-black/50 md:flex-row">
<p>© ۱۴۰۵ سانی‌هوم — تمامی حقوق محفوظ است.</p>
<p className="font-display tracking-wide uppercase">
Designed for Sanihome
</p>
<a
href="https://meshkee.com"
target="_blank"
rel="noopener noreferrer"
dir="ltr"
className="group relative inline-flex items-center gap-2.5 px-2.5 py-1.5 text-black/50 transition-colors duration-300 hover:text-black"
>
<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-black/40 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-black/40 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-black/40 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-black/40 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-60 brightness-0 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-black/70 transition-colors duration-300 group-hover:text-black">
Meshkee E-Commerce Team
</span>
</span>
</a>
</div>
</div>
</footer>
+28 -13
View File
@@ -5,6 +5,7 @@ import { createPortal } from "react-dom";
import type { Category, CategoryNode, CategoryTreeGroup } from "@/lib/meshkee";
import { EmptyState } from "@/components/ui/EmptyState";
import { CartButton } from "@/components/cart/CartButton";
import { ChevronLeftIcon, CloseIcon, MenuIcon, UserIcon } from "@/components/ui/icons";
import { SearchBar } from "./SearchBar";
const navLinks = [
@@ -26,8 +27,8 @@ function Logo({ src, large = false }: { src?: string | null; large?: boolean })
alt="سانی‌هوم"
className={
large
? "h-[42px] w-auto object-contain md:h-[46px]"
: "h-20 w-auto object-contain md:h-24"
? "h-[65px] w-auto object-contain"
: "h-[70px] w-auto object-contain md:h-[86px]"
}
/>
</a>
@@ -107,9 +108,9 @@ function MobileDrawer({
type="button"
onClick={onClose}
aria-label="بستن منو"
className="flex h-9 w-9 items-center justify-center rounded-lg text-xl text-muted transition-colors hover:bg-red-light hover:text-red"
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted transition-colors hover:bg-red-light hover:text-red"
>
✕
<CloseIcon className="h-5 w-5" />
</button>
</div>
@@ -188,7 +189,7 @@ function MobileDrawer({
href={LOGIN_URL}
className="flex items-center gap-2 px-5 py-4 text-right font-medium transition-colors hover:bg-red-light/40 hover:text-red"
>
<span aria-hidden>👤</span>
<UserIcon className="h-4 w-4" />
ورود
</a>
</nav>
@@ -231,9 +232,9 @@ export function Header({
type="button"
onClick={() => setDrawerOpen(true)}
aria-label="منو"
className="flex h-10 w-10 items-center justify-center rounded-lg border border-border text-xl transition-colors hover:border-red hover:text-red min-[1200px]:hidden"
className="flex h-10 w-10 items-center justify-center rounded-lg border border-border transition-colors hover:border-red hover:text-red min-[1200px]:hidden"
>
<span aria-hidden>☰</span>
<MenuIcon className="h-5 w-5" />
</button>
<div className="hidden min-[1200px]:block">
<Logo src={logoUrl} />
@@ -250,16 +251,29 @@ export function Header({
</div>
<div className="flex items-center justify-end gap-3 text-sm">
<div className="hidden items-center gap-2 rounded-2xl bg-surface p-1.5 sm:flex">
<CartButton />
<span className="h-8 w-px bg-border" aria-hidden />
<a
href={LOGIN_URL}
className="hidden items-center gap-1.5 text-muted transition-colors hover:text-red sm:flex"
className="flex items-center gap-2 rounded-xl py-1 pr-1 transition-opacity hover:opacity-80"
>
<span>👤</span>
<span>ورود</span>
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-red text-white">
<UserIcon className="h-5 w-5" />
</span>
<span className="text-right leading-tight">
<span className="block text-sm font-bold whitespace-nowrap text-foreground">
حساب کاربری
</span>
<span className="block text-xs whitespace-nowrap text-muted">ورود/ثبت‌نام</span>
</span>
</a>
</div>
<div className="sm:hidden">
<CartButton />
</div>
</div>
</div>
<div className="border-t border-border py-3 min-[1200px]:hidden">
<SearchBar />
@@ -279,7 +293,7 @@ export function Header({
aria-haspopup="true"
className="flex shrink-0 items-center gap-2 rounded-lg bg-red px-4 py-2 font-medium text-white transition-colors hover:bg-red-dark"
>
<span aria-hidden>☰</span>
<MenuIcon className="h-4 w-4" />
دسته‌بندی‌ها
</button>
@@ -294,9 +308,10 @@ export function Header({
</div>
<a
href="/products"
className="text-xs font-medium text-red hover:text-red-dark"
className="flex items-center gap-1 text-xs font-medium text-red hover:text-red-dark"
>
مشاهده همه ←
مشاهده همه
<ChevronLeftIcon className="h-3.5 w-3.5" />
</a>
</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
// 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 caption under the title. Skip it if the key has no letters at
// all (e.g. it fell back to a bare numeric id).
// English caption beside the title (same inline treatment as every other
// 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 {
const label = key.replace(/[-_]+/g, " ").trim();
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">
<SectionTitle
title={group.title}
enSubtitle={englishLabelFromKey(group.key)}
enTitle={englishLabelFromKey(group.key)}
action="مشاهده همه"
actionHref={`/products?specialId=${group.id}`}
/>
+7 -23
View File
@@ -1,24 +1,16 @@
import { ChevronLeftIcon } from "@/components/ui/icons";
type SectionTitleProps = {
title: 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;
action?: string;
actionHref?: string;
};
export function SectionTitle({
title,
enTitle,
enSubtitle,
subtitle,
action,
actionHref,
}: SectionTitleProps) {
export function SectionTitle({ title, enTitle, subtitle, action, actionHref }: SectionTitleProps) {
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 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>
@@ -32,15 +24,6 @@ export function SectionTitle({
</span>
)}
</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 && (
<p className="mt-1 text-sm text-muted">{subtitle}</p>
)}
@@ -48,9 +31,10 @@ export function SectionTitle({
{action && actionHref && (
<a
href={actionHref}
className="shrink-0 text-sm font-medium text-red transition-colors hover:text-red-dark"
className="flex shrink-0 items-center gap-1.5 rounded-lg bg-red px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
{action} ←
{action}
<ChevronLeftIcon className="h-4 w-4" />
</a>
)}
</div>
+76
View File
@@ -0,0 +1,76 @@
type IconProps = { className?: string };
// Shared light/outline icon set — thin stroke (1.75), rounded caps/joins,
// 24x24 viewBox, matching the chevrons already used in Carousel/ProductGallery.
// Prefer these over emoji anywhere in the UI.
export function MenuIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M4 7h16M4 12h16M4 17h16"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export function UserIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<circle cx="12" cy="8" r="3.25" stroke="currentColor" strokeWidth="1.75" />
<path
d="M4.75 19.25c0-3.45 3.24-6.25 7.25-6.25s7.25 2.8 7.25 6.25"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
/>
</svg>
);
}
export function CartIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M3.5 4.5h1.6l1.1 2M6.2 6.5l1.65 7.3a1.5 1.5 0 0 0 1.46 1.17h6.9a1.5 1.5 0 0 0 1.46-1.15l1.23-5.32H6.2Z"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
<circle cx="10" cy="19.5" r="1.15" stroke="currentColor" strokeWidth="1.6" />
<circle cx="16.5" cy="19.5" r="1.15" stroke="currentColor" strokeWidth="1.6" />
</svg>
);
}
export function CloseIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M6 6l12 12M18 6 6 18"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
/>
</svg>
);
}
export function ChevronLeftIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M14.5 6.5 9 12l5.5 5.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}