376 lines
26 KiB
Markdown
376 lines
26 KiB
Markdown
# 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
|
||
`<h1>`. 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: `<h2 class="modern-slider-item__main-heading">`, `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 `<img src>`, `linkUrl`
|
||
wraps the item in `<a>`, 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 `<details open>` 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 `<table>` in its own focusable
|
||
`.cms-table-scroll` region. The wrapper—not the article/page—owns horizontal scrolling.
|
||
- `data/portfolio.ts` remains temporary local presentation data while this tenant's public
|
||
portfolio list is empty. These visible "products" are portfolio/sample-work records, not
|
||
store products; their public URLs follow `/portfolios/{id}/{titleFaSlug}`.
|
||
- Portfolio detail imagery is rendered by the isolated client component
|
||
`components/portfolio/PortfolioGallery.tsx`; the page and CMS fetching remain server-side.
|
||
It consumes every unique URL assembled from the portfolio's title image, thumbnail, legacy
|
||
image and `images[]`, shows one large image with a thumbnail rail when more than one image
|
||
exists, supports Swiper drag/keyboard controls, pointer-position hover zoom, and an accessible
|
||
full-screen viewer. Do not return to the old two-column grid of equally sized images.
|
||
- The detail page's “محصولات دیگر” row is `RelatedPortfolioCarousel.tsx`: two cards on mobile,
|
||
three on tablet and five on desktop, with touch/keyboard dragging and clickable pagination.
|
||
CMS-backed pages fetch the current category dynamically from `GET /portfolios` and exclude
|
||
the current record; when that category has too few results they append unique items from the
|
||
full portfolio list. This is data-driven—do not restore a fixed four-card grid or hard-code
|
||
future CMS products.
|
||
- This is explicitly **not an e-commerce site** — do not add local login/register/cart/checkout.
|
||
Shared customer authentication is still reflected in the header from the parent-domain
|
||
Meshkee cookies; the actual login/profile UI remains on `customer.hediehmezon.ir`.
|
||
- `src/lib/auth.ts` reconstructs normal or chunked `meshkee_customer_access_token` cookies.
|
||
`GET /api/auth/session` calls official `GET /auth/me` server-side and returns only the name
|
||
and admin flag required by the header. Guests link to `customer.hediehmezon.ir/login`.
|
||
Logged-in users see “{name} عزیز” with profile and logout; admin/owner roles also see
|
||
`business.hediehmezon.ir`. `POST /api/auth/logout` only expires shared access/refresh cookies.
|
||
- `next.config.ts` already sets `output: "standalone"` per the brief's mandatory production
|
||
runtime rule.
|
||
- Do not add local `public/sitemap.xml`, `public/robots.txt`, `app/sitemap.ts`, or
|
||
`app/robots.ts` — Meshkee nginx proxies these.
|
||
- `scripts/generate-sitemap-config.mjs` scans `src/app` before `dev` and `build`, excludes
|
||
dynamic/private/auth/cart/checkout/admin routes, and publishes
|
||
`public/meshkee/sitemap-config.json`. Keep `templates` absent while canonical detail paths
|
||
match Meshkee defaults.
|
||
- `src/lib/paths.ts` is the single source for Persian-preserving canonical slug generation.
|
||
- Every public page has a unique title/description/canonical URL and exactly one visible H1.
|
||
- The remaining site-wide integration is Organization/LocalBusiness JSON-LD from
|
||
`business-info.schema.jsonLd` in the root layout, and per-page SEO meta overrides
|
||
(`seoMetaTitle`/`seoMetaDescription`) per the brief.
|
||
|
||
## Running locally
|
||
|
||
```bash
|
||
npm run dev # Next dev server (Turbopack)
|
||
npm run build # regenerates sitemap-config, then creates the standalone production build
|
||
```
|
||
|
||
A `.claude/launch.json` exists at the `hediyeh-mason/` repo root (one level up) to preview
|
||
this app via the `hediyeh-maison-website` config (`npm --prefix website run dev`).
|
||
|
||
## Product & article cards (source-derived)
|
||
|
||
The visible products are sample works backed by the Meshkee portfolio module. Until portfolio
|
||
records are published, the existing cards use local presentation data under the canonical
|
||
`/portfolios/{id}/{titleFaSlug}` path.
|
||
**Never write "نمونهکار" in the UI** — categories/counts say "محصول" ("۲۶۵ محصول"), nav says
|
||
"محصولات". Cards were rebuilt from the source theme's blade + CSS (the live `/products` and
|
||
`/articles` pages error locally, so read `frames/product_show` / `frames/article_show` and
|
||
`assets/css/theme7b30.css`):
|
||
|
||
- `PortfolioCard`: `.product-grid-item` — portrait image (`padding-bottom:122.6%`, square
|
||
corners), second image cross-fades on hover, hover "مشاهده محصول" button, 18px title, Latin
|
||
name (`titleEn`), category tag. No price/rating/cart. `PortfolioGrid` = sidebar category list
|
||
(counts) + 3-col grid, like the source's `col-lg-3` filter + `col-lg-9` grid.
|
||
- `BlogCard`: `.modern-blog__post` — 15px-radius image (`padding-bottom:84%`), bottom black
|
||
gradient, `category / date` meta + white title overlaid at the bottom. No excerpt/read-time.
|
||
|
||
## Home sections — source-accurate details (round 4)
|
||
|
||
The **page background is white** (`body` has no background in the source theme); only the
|
||
hero (`#e2dbd6`), inner-page subheaders and the category pills use the taupe. Newsletter band
|
||
is `#fafafa`, footer has no background + `border-top:#dedede`. Header is taupe over the home
|
||
hero (until scrolled) and white elsewhere.
|
||
|
||
- **Mobile header/navigation**: the header is 64px tall (`h-16`); the logo sits left and the
|
||
hamburger/login controls sit right, with the hamburger first from the left. Navigation
|
||
opens as an animated white drawer from the right over a blurred overlay. The active route
|
||
uses `bg-cream-deep/50`, not the full-strength cream-deep background.
|
||
|
||
- **Feature strip** (`.features--modern`): 3 items spread in one row (first flush start, last
|
||
flush end), primary-colored 36px icon, 22px title, and **no vertical dividers**. The next
|
||
category section owns the shared hairline divider at every breakpoint.
|
||
- **Categories** (`CategoryCircles`): a real Swiper carousel with 2 slides per view below
|
||
640px, 3 from 640px, and 5 from 1024px. It supports touch/drag and keyboard navigation.
|
||
Mobile/tablet show clickable pagination dots below the cards; desktop hides pagination
|
||
because all five cards fit. Preserve the category artwork's native `272:350` ratio and keep
|
||
labels in normal flow below it—do not compress the art or pull labels upward with negative
|
||
margins. This section owns the divider above it (visible at every breakpoint) and keeps
|
||
responsive top padding on a wrapper outside Swiper between that divider and the artwork;
|
||
do not put this spacing on `.swiper` itself because Swiper's own CSS resets it. CMS-backed
|
||
category cards use `images[].titleFa` beneath the image and omit the local product count.
|
||
Titles keep the source's literal tatweel ("شلــــــــــوار", "پالتــــــــــو",
|
||
"هـــــــــــودی") — no CSS dash. Product PNGs already contain their own pill background,
|
||
so they're rendered directly (no wrapper background, or you get a double ring).
|
||
- **Banners** (`.modern-banners`): **two banners side by side** (`col-lg-6`), 15px radius, image
|
||
ratio 84.5%, centered content: 100px title, 370px description, 34px underline under the link.
|
||
Copy: "کیف / شیک و مجلسی، فقط برای شما" and "لباس / پوشاک، کفش، کیف و لوازم جانبی، اقلام
|
||
ضروری برای این فصل". CMS-backed banners use `titleFa`, `titleEn`, and `subtext` and adopt
|
||
the slot's API-provided aspect ratio.
|