diff --git a/context.md b/context.md
new file mode 100644
index 0000000..9e38b7c
--- /dev/null
+++ b/context.md
@@ -0,0 +1,375 @@
+# context.md — هدیه مزون (Hediyeh Maison)
+
+Living context file for AI assistants and future contributors. **Update this file on every
+prompt whenever the project changes** (new pages, data source swaps, design decisions).
+
+## What this project is
+
+A Next.js rebuild of the original Laravel/Blade "Hediyeh Maison" storefront
+(`../hediyeh-maison`, a Meshkee web-builder site). The original was a clothing **store**;
+this rebuild repurposes the same visual identity into a **portfolio / showcase site** — no
+cart, checkout, or product purchasing. Portfolio content is still local showcase data; Blog
+listing/detail content is connected to the public Meshkee Website API for `hediehmezon.ir`.
+While that tenant has no published articles, three clearly labelled local sample articles are
+shown; they disappear automatically as soon as the CMS returns any published article.
+
+- Language: Persian (`fa`) only, `dir="rtl"`.
+- Framework: Next.js (App Router), TypeScript, Tailwind CSS v4.
+- Carousel: Swiper (`swiper/react`).
+- Icons: Font Awesome (`@fortawesome/react-fontawesome` + free-solid / free-regular / free-brands).
+- Fonts: **Iran Yekan** (self-hosted `next/font/local`, all Persian text — body, UI, every
+ heading) + **Valky** (self-hosted `next/font/local`, Latin-only display face — used *only*
+ for the hero's "Hediyeh" / "MAISON" words). See `src/app/fonts.ts`.
+
+## Fonts — do not use cdn.meshkee.com, do not use Google Fonts for the display face
+
+The source theme (`hediyeh-maison/settings.php`, `metadata.php`) references
+`cdn.meshkee.com/web-builder/fonts/...`. **These links are unreachable from this
+environment** — confirmed by the user; don't attempt to fetch from `cdn.meshkee.com`, it
+will hang/fail.
+
+- Persian body/UI face: **Iran Yekan**, per explicit user instruction — self-hosted `.woff`
+ files copied from `../../raoofi/src/assets/fonts/` into `src/assets/fonts/`.
+- Latin display face: the live source site's hero heading computes to
+ `font-family: Valky` at `font-size: 300px` (confirmed via `getComputedStyle` on
+ `http://hediyeh-maison.local:8000/`). The user placed `Valky Regular.ttf` in `public/` —
+ it now lives at `src/assets/fonts/valky-regular.ttf`, loaded via `next/font/local` as the
+ `valky` export (`--font-valky` / `.font-latin` utility class in `globals.css`).
+ **An earlier version of this site used Google's Playfair Display as a substitute — that
+ was wrong and was replaced.** `.font-latin` (Valky) must only wrap genuine Latin/English
+ copy (currently: the Hero's "Hediyeh" and "MAISON"). Every Persian heading uses plain
+ `font-bold`/`font-extrabold` on the default Iran Yekan body font — Valky has no Persian
+ glyphs, so applying it to Persian text silently falls back to a generic serif and looks
+ wrong. If you add new Latin display copy, use `.font-latin`; if it's Persian, don't.
+
+## Content & asset provenance
+
+The Blade project's theme-level static images (`hediyeh-maison/assets/img/**`) are **broken
+Git LFS pointers** (never pulled — `git lfs pull` fails, git.meshkee.com unreachable from
+this environment). Do not copy from `assets/img/`.
+
+The **real, working photography** lives in `hediyeh-maison/assets/files/` (hashed filenames,
+CMS-uploaded media, valid binaries). These were identified by inspecting the live rendered
+DOM (`img.currentSrc`) on the local dev site (`http://hediyeh-maison.local:8000`, ports
+proxied through the Blade app) and copied into `public/images/` with descriptive names.
+Mapping (source hash → usage):
+
+| Source file (`assets/files/`) | Used as |
+|---|---|
+| `PGnhGwcJfR.png` | `public/images/brand/logo.png` (header + footer) |
+| `cYbVH0Fg4Z.png` | `public/images/brand/monogram.png` (small cursive "H" mark, unused so far) |
+| `X8XbnYcoSl.jpg` | `public/images/hero/hero-1.jpg` |
+| `VEs3mZuCir.jpg` | `public/images/hero/hero-2.jpg` |
+| `WfD9E9TJd1.png` | `public/images/categories/trousers.png` (شلوار) |
+| `DbVccNhN6N.png` | `public/images/categories/sunglasses.png` (عینک آفتابی) |
+| `VEVtsmcNRu.png` | `public/images/categories/coat.png` (پالتو) |
+| `PzlA8CwvA8.png` | `public/images/categories/hoodie.png` (هودی) |
+| `G6U04V8fhX.png` | `public/images/categories/scarf.png` (شال و روسری) |
+| `ptZvorsSiP.jpg` | `public/images/banners/banner-bags.jpg` (کیف editorial banner) |
+| `o8x3phkJEQ.jpg` | `public/images/banners/banner-clothing.jpg` (لباس editorial banner) |
+| `1L3fCdTL2g.png` | `public/images/about/about-image.png` |
+| `7xIvsBoGth.png` | `public/images/about/signature.png` |
+| `uT9b3ZoiSs.jpg` | `public/images/about/storefront.jpg` (⚠ shows a *different* real shop's signage — "UTERO" — currently unused in any page; do not display it as Hediyeh Maison's own storefront) |
+| `2SLidM7KQV.jpg`, `5vCbHMQbmt.jpg`, `6ncMx3U2nt.jpg` | `public/images/interior/*.jpg` (boutique interior shots, reused across about/portfolio/blog) |
+| `pHcCJphsTp.png`, `oryXTQOfe0.png`, `swJLLftw08.png`, `1t8I5IZoob.png`, `kTwhmXLhxe.png` | `public/images/portfolio/*-alt.png` (duplicate product angles, used as extra portfolio/blog imagery) |
+
+Several other files in `assets/files/` are unrelated demo leftovers (cake/bakery photos,
+duplicate wavy-pattern subheader banners with baked-in **English** copy like "About Us",
+"Contact Us") and were intentionally **not** used — see next section.
+
+### Subheader banners were rebuilt as SVG, not reused as images
+
+The source site's inner-page hero banners (`assets/files/5qiwBQYHMA.jpg` and several
+duplicates) are a cream background with a wavy line texture **and English text baked into
+the raster** ("About Us", "Contact Us", "Products", "Articles", "Article Details",
+"Product Details"). Since this site is Persian/RTL with different headings, reusing those
+images verbatim would show mismatched/wrong-language text. Instead, `WavePattern.tsx`
+recreates the line texture as inline SVG, composed in `PageHero.tsx` with our own Persian
+`
`. This is the pattern used for About/Portfolio/Blog/Contact hero banners.
+
+### Hero — rebuilt to match the live source structure exactly
+
+An earlier version of the hero was a loose reinterpretation (wrong font, a single centered
+tagline + invented CTA button, MAISON pinned to the bottom, opacity-faded giant text) and
+the user flagged it as "nothing like the original." It was rebuilt from the *actual* live
+DOM of `http://hediyeh-maison.local:8000/` (`.modern-slider-item__*` classes), read via
+`getComputedStyle`/`getBoundingClientRect`, not from a screenshot guess. Ground truth:
+
+- Giant background word: `
`, `font-family:
+ Valky`, `font-size: 300px`, `color: rgb(180,144,118)` (= `--color-primary`, **full
+ opacity**, not faded) — full-bleed, allowed to overflow both edges of the viewport since
+ it's wider than the container. `Hero.tsx` reproduces this as an absolutely-positioned
+ `.font-latin` span using responsive fixed sizes and `text-primary` (no `/opacity`). The
+ current compact desktop direction caps it at 220px/240px rather than the source's 300px.
+- "MAISON": also Valky, white, `font-size: 48px` (≈16% of the main heading), positioned
+ around **64% down the photo**, not flush to the bottom.
+- The arch photo's real aspect ratio is `480:820` (tall/narrow, ≈0.585), not a generic
+ 3:4 crop — `Hero.tsx` uses `aspect-[480/820]`.
+- The two Persian white pill labels that flanked the central image were removed per the
+ current design direction; do not restore them unless explicitly requested.
+- There is **no CTA button** in the source hero — the earlier "مشاهده نمونهکارها" button
+ was invented and has been removed; portfolio access stays in the nav.
+- `hero-2.jpg` (`VEs3mZuCir.jpg`) is a genuinely black-and-white editorial photo in the
+ source data, not a bug — don't "fix" it to color.
+- A small `.modern-slider-item__circle` decorative badge (~117×117px, top-right of the
+ photo) exists in the source but its image asset is itself a broken/404 link even on the
+ live site — intentionally not reproduced.
+
+If the hero still looks off, re-diff against the live site with the same
+`getComputedStyle`/DOM-read approach rather than eyeballing a screenshot — the CMS positions
+things with absolute px/percent values that are easy to misjudge visually.
+
+**Round 2 fixes** (user supplied a screenshot of the real source hero at a wide desktop
+viewport and it still looked "nothing like" this site — correctly, two things were still
+wrong):
+
+1. **Missing outline ring.** The source photo has a second, larger arch-shaped thin outline
+ offset outward around it (visible clearly at the top and, more, below the photo's own
+ bottom edge). This isn't reproducible from the source DOM (the one plausible source
+ element, `.modern-slider-item__circle`, is a small unrelated ~117×117 badge whose own
+ image 404s even on the live site) — it's rebuilt directly as a CSS decoration: an
+ `aria-hidden` absolutely-positioned sibling div, same arch shape, offset via negative
+ inset (`-inset-3 -bottom-6` scaling up to `-inset-5 -bottom-12` at `lg`), `border
+ border-primary/45`.
+2. **Photo/text sized like the 753px-wide measurement, everywhere.** The first pass
+ measured the source at a narrow ~753px pane and used those proportions (photo capped
+ ~260px, text via `vw`) at *every* viewport. But the source uses **fixed, non-fluid**
+ px values above some breakpoint — re-measuring the source at 1585px showed the image at
+ its **native 480×820**, not scaled down. The current compact implementation steps the
+ photo width through `42vw` → `300px` → `340px` → `380px`. The slide now uses normal-flow
+ top/bottom padding around the artwork, so its height follows `slot.aspectRatio` instead of
+ relying on hard-coded slide heights. This keeps the image close to the header and the
+ pagination close to the image even if the CMS ratio changes.
+ Also: a stray `max-w-[210px]` on the photo wrapper was
+ accidentally left active at *every* breakpoint (Tailwind doesn't auto-clear an
+ unprefixed utility at larger breakpoints — a later `lg:w-[380px]` does not remove an
+ earlier bare `max-w-[210px]`), silently capping the photo at 210px regardless of
+ viewport. Watch for this pattern elsewhere: any bare (unprefixed) sizing utility mixed
+ with responsive overrides needs an explicit `sm:max-w-none`-style reset, or just don't
+ set it unprefixed at all.
+
+**Round 3 fix (exact source values, read via `getComputedStyle`)**: the photo `img` has
+`border-radius: 1000px` on *all* corners (a full pill — rounded bottom too, not just the
+top arch), and the outline ring is `.modern-slider-item__image::before` with
+`border-radius: 1000px`, `1px solid rgb(199,189,182)` (`#c7bdb6`), `inset: -20px` uniformly
+(same offset at the bottom as the top — an earlier version wrongly extended it lower).
+`Hero.tsx` now uses `rounded-[1000px]` on both the photo and the ring, ring color
+`border-[#c7bdb6]`.
+
+When verifying the hero visually, take the screenshot at a genuinely wide viewport
+(`resize_window` with an explicit `width`/`height`, e.g. 1280×760+ — not the Browser pane's
+default responsive size, which is much narrower and misrepresents how the fixed-px hero
+reads at real desktop widths) — and take it twice if the first one looks blank/broken,
+since this pane occasionally serves a stale/mid-transition frame during Swiper autoplay.
+
+### Meshkee footer credit (bottom bar)
+
+Per user instruction, the footer's bottom row matches **https://safeteb.com/** exactly:
+site name on one side, a `meshkee-credit` link (hover border animation, Meshkee logo,
+"Designed And Developed By" / team name) on the other, linking to `https://meshkee.com`.
+CSS was extracted live from safeteb.com and ported into `globals.css`
+(`.footer-bottom-bar`, `.meshkee-credit*` rules). The logo asset was downloaded from
+`https://safeteb.com/images/footer/MeshkeeLogo-White.png` →
+`public/images/brand/meshkee-logo-white.png`. Implementation: `components/layout/Footer.tsx`.
+
+### Real site content vs. placeholder data
+
+The source Blade site (`hediyeh-maison/custom/fa/pages.php`,
+`frames/*/config.php`) is mostly the **Meshkee web-builder demo/default theme** — most
+config-level text is literal placeholder ("متن تستی", lorem ipsum, fake nav items like
+"صفحه ی آماده 1..29"). The actually meaningful content came from what renders live on the
+dev server (DB-driven overrides), captured via browser inspection:
+
+- Home: hero taglines, feature strip (support/variety/secure payment — adapted, see below),
+ 5 category tiles (شلوار/عینک آفتابی/پالتو/هودی/شال و روسری with real counts), 2 editorial
+ banners (کیف/لباس), newsletter section.
+- About: founded year 1986, 3× 95% stats, tagline "از طبیعت، پاستیل رنگی و فعالیتهای روزانه
+ الهام گرفتهایم."
+- Contact: address "قم، سالاریه، میدان پیچک", phone `09123456789`, email `name@info.com`
+ (footer's placeholder address differed from the contact page's; the contact page's address
+ was kept as the single canonical one across footer + contact, per normal practice —
+ see `lib/site-config.ts`).
+- Articles (`/articles`) page could not be inspected — it errors locally (TLS error calling
+ `www.meshkee.ir/api/v1/get-articles`). **Per explicit user instruction, blog and portfolio
+ card designs are original work**, not cloned from a live reference. Blog data now comes
+ from Meshkee through `src/lib/meshkee.ts`; product showcase data remains in
+ `data/portfolio.ts`.
+
+### Deliberate content adaptations (store → portfolio)
+
+Since the site is no longer a store:
+- Nav "محصولات" (Products) → "نمونهکارها" (Portfolio); "مقالات" kept as Blog.
+- Feature strip's "اکنون بخرید، بعدا پرداخت کنید" (buy-now-pay-later) dropped — replaced with
+ "مشاوره تخصصی" (expert consultation), keeping support + variety as-is.
+- Category tile counts relabeled "XX مورد" → "XX نمونهکار".
+- Editorial banner CTAs "خرید" / "همین حالا خرید کنید" → "مشاهده نمونهکارها".
+- Newsletter/"club" CTA "عضویت" kept (generic, still applies to a portfolio site).
+- Newsletter remains client-side only. Contact submissions are proxied server-side by
+ `src/app/api/contact/route.ts` to
+ `POST /tenants/{domain}/contact-submissions`, with validation and reset/success states.
+
+## Design tokens (`src/app/globals.css`, `@theme` block)
+
+`--color-primary`/`secondary`/`tertiary` come from the original theme's `settings.php`.
+`--color-cream` is **not** a guess — it's the live-computed background color of the source
+hero section (`getComputedStyle` → `rgb(226,219,214)`), so this now matches the original's
+actual muted taupe-grey rather than a warmer generic "cream":
+
+```
+--color-cream: #e2dbd6 body background (source hero bg, computed live)
+--color-cream-deep: #d5c9c0 section/footer background (darker shade of the above)
+--color-primary: #b49076 CTA, accents, big display text (source primaryColor)
+--color-primary-dark: #8f6f58 hover state
+--color-secondary: #8fa06c sparse accent (source secondaryColor, muted)
+--color-tertiary: #2a9fc9 sparse accent (source tertiaryColor, muted)
+--color-ink: #241f1a body text
+--color-ink-soft: #6c6259 muted text
+--color-slate: #5c6873 one-off accent — the source hero's right-side tagline text
+ color (computed live), used only there
+--color-border: #d3c5bb
+```
+
+## Page inventory
+
+| Route | Notes |
+|---|---|
+| `/` | Hero (Swiper), feature strip, category circles, editorial banners, newsletter |
+| `/portfolios` | Portfolio-backed products/sample works. Sidebar category list + 3-col card grid, reads `?category=` |
+| `/portfolios/[id]/[titleFaSlug]` | Canonical portfolio detail path via `portfolios/by-id/{id}` with local fallback while CMS is empty; stale title slugs redirect; right-side interactive gallery with a large image, clickable thumbnails, slide navigation, hover zoom and an accessible full-screen viewer |
+| `/blog` | Real Meshkee CMS articles; temporary sample cards only while the API list is empty |
+| `/blog/[id]/[titleSlug]` | CMS detail from `blogs/by-id/{id}`; redirects a stale title slug to its canonical path; accessible breadcrumb, rich text/tables and sticky related accordion |
+| `/about` | Founded-year stat, story, 3 stats, closing image band |
+| `/contact` | Info list, embedded Google Maps iframe (no API key needed, `output=embed`), contact form |
+
+## Meshkee Website API integration
+
+Per `https://api.meshkee.com/docs/website/AI_PROMPT.md` (the Meshkee Website API brief —
+read this before touching data fetching):
+
+- Canonical tenant domain: `hediehmezon.ir`; `GET /tenants/hediehmezon.ir` resolves to
+ business id `50` and confirms the `blog` module is enabled. Override locally with
+ `MESHKEE_WEBSITE_DOMAIN` when necessary.
+- Home static media is fetched once by `src/app/page.tsx` through
+ `GET /tenants/{websiteDomain()}/website/static-images?pageKey=home`. It always uses the
+ project's central `websiteDomain()` resolver—never a second/hard-coded tenant. The page
+ matches `slider`, `categories`, and `two-banner-below-slider`; list slots render every
+ image and single slots render only `images[0]`. Empty slots, API errors, or missing keys
+ retain the existing local imagery. `url` is rendered as native ``, `linkUrl`
+ wraps the item in ``, and the API `aspectRatio` controls the media box.
+- `GET /meshkee/static-image-slots` publishes the dashboard catalog: duplicatable `slider`,
+ duplicatable `categories`, and a two-item `two-banner-below-slider`. **Whenever a static
+ media box's rendered size or aspect ratio changes, update this catalog in the same change**
+ (`aspectRatio` and, when relevant, `recommendedWidth` / `itemCount`). Never invent upload
+ or dashboard write APIs.
+- `src/lib/meshkee.ts` owns server-only blog reads. It resolves the tenant first, then uses
+ `GET /tenants/{domain}/blogs` and `GET /tenants/{domain}/blogs/by-id/{id}` with five-minute
+ Next data-cache revalidation. Detail pages honor API 404 via Next `notFound()`, emit CMS
+ `schema.jsonLd`, and prefer `seoMetaTitle` / `seoMetaDescription`.
+- Related articles are fetched with the current article's `categoryId`, current id excluded,
+ and at most five items rendered. The native `` accordion avoids shipping a
+ Client Component solely for collapse behavior.
+- `src/data/fallback-blogs.ts` contains explicitly labelled sample copy because the tenant had
+ zero published articles on 2026-09-29. `/blog` uses it only when the real list is empty;
+ detail fallback ids are also disabled as soon as the CMS list becomes non-empty.
+- `DetailPageShell.tsx` is shared by blog and product canonical detail pages; do not re-create
+ detail-page max width, horizontal padding, vertical padding, or breadcrumb placement inside
+ individual detail pages.
+- CMS rich text uses `.cms-rich-text` in `globals.css`. `normalizeArticleHtml()` supplies a
+ meaningful fallback alt to CMS images and wraps every incoming `
+
+ );
+}
diff --git a/src/components/shared/WavePattern.tsx b/src/components/shared/WavePattern.tsx
new file mode 100644
index 0000000..0d5db11
--- /dev/null
+++ b/src/components/shared/WavePattern.tsx
@@ -0,0 +1,48 @@
+/**
+ * Decorative wavy-line texture, redrawn in SVG in the spirit of the source
+ * theme's subheader banners (assets/files/*.jpg — cream ground with fine
+ * flowing lines). Rebuilt as vector so it stays crisp and RTL-agnostic
+ * instead of shipping the raster banners (which had English copy baked in).
+ */
+export default function WavePattern({ className = "" }: { className?: string }) {
+ const rows = 16;
+ const width = 900;
+ const height = 260;
+ const gap = height / (rows - 1);
+
+ const paths = Array.from({ length: rows }, (_, i) => {
+ const y = i * gap;
+ const wobble = 26 + (i % 3) * 6;
+ const d = `M0,${y} C ${width * 0.18},${y - wobble} ${width * 0.32},${y + wobble} ${width * 0.5},${y} S ${width * 0.82},${y - wobble} ${width},${y}`;
+ return ;
+ });
+
+ return (
+
+ );
+}
diff --git a/src/data/fallback-blogs.ts b/src/data/fallback-blogs.ts
new file mode 100644
index 0000000..58004b4
--- /dev/null
+++ b/src/data/fallback-blogs.ts
@@ -0,0 +1,85 @@
+import type { MeshkeeBlog } from "@/lib/meshkee";
+
+export const fallbackBlogs: MeshkeeBlog[] = [
+ {
+ id: "fallback-style-guide",
+ businessId: 50,
+ title: "راهنمای انتخاب لباس برای استایل روزمره",
+ titleFa: "راهنمای انتخاب لباس برای استایل روزمره",
+ slug: "sample-daily-style-guide",
+ abstract:
+ "این مقالهی تستی برای نمایش ساختار صفحه است و پس از انتشار مقالههای مزون، خودکار با محتوای CMS جایگزین میشود.",
+ mainTextHtml: `
+
انتخاب لباس روزمره زمانی سادهتر میشود که راحتی، رنگ و فرم را در کنار هم ببینیم. در این راهنما چند نکتهی کاربردی برای ساختن یک استایل هماهنگ مرور میکنیم.
+
از رنگهای پایه شروع کنید
+
رنگهای خنثی پایهای منعطف برای ترکیبهای مختلف میسازند. سپس میتوانید با یک اکسسوری یا لایهی رنگی، شخصیت بیشتری به ظاهر خود بدهید.
+
لباسی را انتخاب کنید که علاوه بر زیبایی، با ریتم زندگی روزانهی شما هماهنگ باشد.
+
چکلیست انتخاب لباس
+
+
تناسب فرم لباس با موقعیت استفاده
+
هماهنگی رنگها با یکدیگر
+
راحتی پارچه در استفاده طولانی
+
+
مقایسهی پیشنهادی
+
+
موقعیت
پارچه
رنگ پایه
لایه دوم
اکسسوری پیشنهادی
+
+
روزمره
نخ و پنبه
کرم
کت سبک
شال ساده
+
محیط کار
ترکیبی
سرمهای
مانتوی ساختارمند
کیف مینیمال
+
دورهمی
ساتن مات
مشکی
رویه آزاد
زیورآلات ظریف
+
+
+
برای دیدن تازهترین نوشتهها همیشه میتوانید به فهرست مقالات برگردید.