diff --git a/AGENTS.md b/AGENTS.md
index 9aa3e90..16b231a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,7 +19,7 @@ fallbacks should update it.
empty or failing module degrades one section instead of the page.
- Never invent CMS/admin endpoints. If OpenAPI and any prose conflict, OpenAPI wins.
-### What the tenant actually returns today (2026-08-31)
+### What the tenant actually returns today (2026-09-06)
| Endpoint | State |
|---|---|
@@ -27,9 +27,10 @@ fallbacks should update it.
| `/products` | **479 items**, real titles, images, categories |
| `/categories?entityType=product` | **48 items**, 3 levels under «کالای دیجیتال». Products are filed on **leaf** categories only — a parent category page/carousel must query its whole subtree (`subtreeIds`/`subtreeIdsAll` in `src/lib/categoryTree.ts`). |
| `/store-items`, `/store-specials` | **empty** → no prices, no discounts, everything `inStock:false`. `/store-items/by-product/{id}` returns a single `{ storeItem }` (or null) — for a product's full variant list use `/store-items?productId=&pageSize=100` (`{ items }`) instead, see `getStoreItemsByProduct` in `src/lib/api.ts`. |
-| `/website/sliders`, `/website/static-images`, `/website/category-groups` | **empty** |
+| `/website/sliders`, `/website/category-groups` | **empty** |
+| `/website/static-images` | **filled** — `slider` (2), `categories` (8 of 15), `one/two/three-banner-bg` all have real images. Verified live and rendering correctly; if a future check finds otherwise, suspect a stale build/deploy before suspecting the wiring. |
| `/blogs` | **empty** (module enabled) |
-| `/website/business-info`, `/website/favicon` | present but blank (no logo, no phone, no socials) |
+| `/website/business-info` | phones/addresses/socialMedia still empty; `logoUrl`/`faviconUrl` are now set but point to a **different business's** mark (see "Brand") |
| `/products/by-id/{id}/technical-info` | `{ form: null, values: [] }` — empty on every product checked so far |
| `/comments` | **empty**, but `POST` works (no bearer required by the API itself) |
@@ -38,18 +39,31 @@ fallbacks should update it.
| Gap | Fallback | Remove when |
|---|---|---|
| No prices | `src/lib/demoPricing.ts`, gated by `NEXT_PUBLIC_DEMO_PRICING=1` — deterministic price per product id | store-items exist |
-| No sliders | `src/app/page.tsx`'s local `FALLBACK_SLIDES` (banners downloaded from the live moalem.shop theme into `public/images/banners/`) — used only when the `slider` static-image slot is also empty, see "Static-image slots" below | `/website/sliders` or the `slider` slot returns items |
| No blog posts | `src/data/fallbackBlogs.ts` — the real posts from moalem.shop/articles, shown on the homepage and `/blog` as non-clickable cards (no real detail page exists for them) | `/blogs` returns items |
-| No category art | placeholder SVGs in `public/images/categories/{id}.svg`, generated by `scripts/gen-category-placeholders.mjs` — overridden per-slide by the `categories` static-image slot when it has images | real thumbnails are dropped in, or the `categories` slot is filled |
-| No branding in API | logo/favicon copied from the live theme into `public/` | `logoUrl` is set on the tenant |
-| No banner art (`one/two/three-banner-bg`) | `src/components/PromoBanners.tsx` renders a flat dashed placeholder per slot cell, labelled with the recommended size/ratio | the matching static-image slot is filled |
+| No category art past slot position 8 | placeholder SVGs in `public/images/categories/{id}.svg`, generated by `scripts/gen-category-placeholders.mjs` for every category at every level, not just level-2 | the `categories` slot has an image for that position |
+| Wrong logo/favicon in API | `getFavicon()` is wired live (see "Brand") but the uploaded asset itself is wrong; no code fallback needed once the dashboard upload is fixed | the correct asset is uploaded |
## Brand
-Taken from the live moalem.shop theme, not invented:
+Colors taken from the live moalem.shop theme, not invented:
primary `#E51E22`, dark `#B10123`, ink `#2E2E2E`, body `#7C7C7C`.
Fonts: IRANYekan (fa, local `public/fonts`) + Montserrat (latin, next/font).
+- Logo/favicon: `getFavicon()` in `src/lib/api.ts` reads
+ `GET /tenants/moalem.shop/website/favicon` — the documented single source —
+ and `Header.tsx`/`Footer.tsx`/`generateMetadata()` in `layout.tsx` all use
+ it, falling back to the bundled `/images/logo.png` / `/favicon.png` only
+ when the API field is null. **As of 2026-09-06 the dashboard has a logo/
+ favicon uploaded, but it's the wrong business's** (a home-appliances store's
+ mark, not موبایل معلم) — the wiring is correct and will pick up the right
+ asset automatically the moment it's replaced in the dashboard; don't
+ "fix" this by hardcoding the local fallback again.
+- Contact data (phone, mobile, branch addresses, Instagram/Telegram) in
+ `src/data/site.ts` is real, pulled from the live theme's footer — `GET
+ /website/business-info` still returns empty `phoneNumbers`/`addresses`/
+ `socialMedia` for this tenant, so these stay hand-maintained here until
+ that's filled in. Update both together when it is.
+
## Layout conventions
- Content width `--container-boxed: 1200px`, applied with the `boxed` utility.
@@ -127,6 +141,24 @@ layout): hero → categories → hot deals → **three-banner-bg** → موبا
carousel → **two-banner-bg** → لوازم جانبی → لپ تاپ → **one-banner-bg** →
کنسول بازی → مجله.
+## Static pages
+
+- `/about` — real copy transcribed from the live moalem.shop theme's
+ about-us module (verified against its cached HTML, not invented); brand
+ strip is plain text wordmarks (no scraped logo images — avoids a
+ trademark/asset-licensing headache for names we don't have real logo files
+ for) with real product photos pulled from the API.
+- `/contact` — `ContactForm.tsx` posts to the real `POST /contact-submissions`
+ (no bearer required); branch cards use `BRANCHES` from `src/data/site.ts`
+ plus a key-less Google Maps `output=embed` iframe per branch (no API key
+ needed for a query-based embed).
+- `/installments` — flat, brand-colored hero (no photo, no gradient — this
+ site stays flat) instead of the reference design's photographic banner;
+ category highlights reuse the same `categories` static-image slot images
+ as the homepage carousel, matched by the same positional index. One
+ reference category ("خانه هوشمند") has no equivalent on this tenant and is
+ swapped for "لوازم برقی" — see `HIGHLIGHT_IDS` in the page for the mapping.
+
## Product / blog pages
- `/products` — full catalog, `?name=` search, `?page=`.
diff --git a/public/images/categories/1227.svg b/public/images/categories/1227.svg
new file mode 100644
index 0000000..52cce2a
--- /dev/null
+++ b/public/images/categories/1227.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1228.svg b/public/images/categories/1228.svg
new file mode 100644
index 0000000..6c331da
--- /dev/null
+++ b/public/images/categories/1228.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1229.svg b/public/images/categories/1229.svg
new file mode 100644
index 0000000..c6a4f98
--- /dev/null
+++ b/public/images/categories/1229.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1230.svg b/public/images/categories/1230.svg
new file mode 100644
index 0000000..e447fec
--- /dev/null
+++ b/public/images/categories/1230.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1231.svg b/public/images/categories/1231.svg
new file mode 100644
index 0000000..403349b
--- /dev/null
+++ b/public/images/categories/1231.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1232.svg b/public/images/categories/1232.svg
new file mode 100644
index 0000000..1009fd2
--- /dev/null
+++ b/public/images/categories/1232.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1233.svg b/public/images/categories/1233.svg
new file mode 100644
index 0000000..fc85788
--- /dev/null
+++ b/public/images/categories/1233.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1234.svg b/public/images/categories/1234.svg
new file mode 100644
index 0000000..9a95ad8
--- /dev/null
+++ b/public/images/categories/1234.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1235.svg b/public/images/categories/1235.svg
new file mode 100644
index 0000000..59d18e2
--- /dev/null
+++ b/public/images/categories/1235.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1236.svg b/public/images/categories/1236.svg
new file mode 100644
index 0000000..b983e60
--- /dev/null
+++ b/public/images/categories/1236.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1237.svg b/public/images/categories/1237.svg
new file mode 100644
index 0000000..192629a
--- /dev/null
+++ b/public/images/categories/1237.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1238.svg b/public/images/categories/1238.svg
new file mode 100644
index 0000000..4ebaa87
--- /dev/null
+++ b/public/images/categories/1238.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1239.svg b/public/images/categories/1239.svg
new file mode 100644
index 0000000..7c2d465
--- /dev/null
+++ b/public/images/categories/1239.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1240.svg b/public/images/categories/1240.svg
new file mode 100644
index 0000000..d208d6c
--- /dev/null
+++ b/public/images/categories/1240.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1241.svg b/public/images/categories/1241.svg
new file mode 100644
index 0000000..94d3fbb
--- /dev/null
+++ b/public/images/categories/1241.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1242.svg b/public/images/categories/1242.svg
new file mode 100644
index 0000000..08b8cd3
--- /dev/null
+++ b/public/images/categories/1242.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1243.svg b/public/images/categories/1243.svg
new file mode 100644
index 0000000..32accc5
--- /dev/null
+++ b/public/images/categories/1243.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1244.svg b/public/images/categories/1244.svg
new file mode 100644
index 0000000..3127090
--- /dev/null
+++ b/public/images/categories/1244.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1245.svg b/public/images/categories/1245.svg
new file mode 100644
index 0000000..e02e457
--- /dev/null
+++ b/public/images/categories/1245.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1246.svg b/public/images/categories/1246.svg
new file mode 100644
index 0000000..dd25a9c
--- /dev/null
+++ b/public/images/categories/1246.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1247.svg b/public/images/categories/1247.svg
new file mode 100644
index 0000000..ca79bad
--- /dev/null
+++ b/public/images/categories/1247.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1248.svg b/public/images/categories/1248.svg
new file mode 100644
index 0000000..5e26c3a
--- /dev/null
+++ b/public/images/categories/1248.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1249.svg b/public/images/categories/1249.svg
new file mode 100644
index 0000000..46c11fb
--- /dev/null
+++ b/public/images/categories/1249.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1250.svg b/public/images/categories/1250.svg
new file mode 100644
index 0000000..d0361b0
--- /dev/null
+++ b/public/images/categories/1250.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1252.svg b/public/images/categories/1252.svg
new file mode 100644
index 0000000..2c7b83c
--- /dev/null
+++ b/public/images/categories/1252.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1257.svg b/public/images/categories/1257.svg
new file mode 100644
index 0000000..a489def
--- /dev/null
+++ b/public/images/categories/1257.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1262.svg b/public/images/categories/1262.svg
new file mode 100644
index 0000000..99131ef
--- /dev/null
+++ b/public/images/categories/1262.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1265.svg b/public/images/categories/1265.svg
new file mode 100644
index 0000000..7d20c21
--- /dev/null
+++ b/public/images/categories/1265.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1266.svg b/public/images/categories/1266.svg
new file mode 100644
index 0000000..aadc5df
--- /dev/null
+++ b/public/images/categories/1266.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1267.svg b/public/images/categories/1267.svg
new file mode 100644
index 0000000..493b761
--- /dev/null
+++ b/public/images/categories/1267.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/categories/1270.svg b/public/images/categories/1270.svg
new file mode 100644
index 0000000..1f6f23f
--- /dev/null
+++ b/public/images/categories/1270.svg
@@ -0,0 +1,6 @@
+
diff --git a/scripts/gen-category-placeholders.mjs b/scripts/gen-category-placeholders.mjs
index 2251efa..800c8f0 100644
--- a/scripts/gen-category-placeholders.mjs
+++ b/scripts/gen-category-placeholders.mjs
@@ -1,6 +1,9 @@
-// Writes one placeholder thumbnail per top-level product category into
-// public/images/categories/{id}.svg. They are meant to be replaced with real
-// artwork one file at a time — the carousel reads the same paths either way.
+// Writes one placeholder thumbnail per product category (every level, not
+// just level-2) into public/images/categories/{id}.svg. They are meant to be
+// replaced with real artwork one file at a time — the carousel and any other
+// page that links a specific category id (e.g. /installments) read the same
+// paths either way, so every id needs a file, not just the level-2 ones the
+// homepage carousel shows by default.
// Run: node scripts/gen-category-placeholders.mjs
import { mkdir, writeFile } from "node:fs/promises";
@@ -12,14 +15,17 @@ const res = await fetch(
);
const { items } = await res.json();
-const roots = items.filter((c) => c.parentId === null).map((c) => c.id);
-const level2 = items.filter((c) => c.parentId && roots.includes(c.parentId));
+// Root categories (parentId === null) have no icon of their own on this
+// site — nothing links directly to "کالای دیجیتال" itself — so only their
+// descendants get a placeholder.
+const roots = new Set(items.filter((c) => c.parentId === null).map((c) => c.id));
+const withIcons = items.filter((c) => !roots.has(c.id));
await mkdir(OUT, { recursive: true });
const escape = (s) => s.replace(/&/g, "&").replace(//g, ">");
-for (const cat of level2) {
+for (const cat of withIcons) {
const label = escape(cat.nameFa || cat.name);
const svg = `