diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..c934869 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,117 @@ +# Ayoubi Gallery — AI / Project Context + +## Source of truth +- Live domain: `https://ayoubi.jewelry` +- Design language: black ground, ivory type, gold lines, Doran + Cinzel, flat surfaces +- Backend: https://api.meshkee.com/docs/website +- Domain: `ayoubi.jewelry` (`NEXT_PUBLIC_WEBSITE_DOMAIN`) +- API base: `https://api.meshkee.com/api/v1` (`NEXT_PUBLIC_MESHKEE_API_BASE`) +- Tenant business id: `42` (`GET /tenants/ayoubi.jewelry`, nameFa: گالری ایوبی فخیم) +- Enabled modules: `products`, `blog`, `finance`. `specialProductsSource`: `store_item` +- Prompt reference: `https://api.meshkee.com/api/v1/public/ai-prompts/ayoubi-gallery/backend/253d39eea2a4047c39296d1a` + +## Stack +- Next.js 15 App Router + React 19 + TypeScript +- `output: "standalone"` in `next.config.ts` (required for the Meshkee websites VM) +- Language: `fa`, direction: `rtl` +- Fonts: Doran (`--font-doran`), Cinzel (`--font-cinzel`) +- Styling: `src/app/globals.css` only (no Tailwind) + +## Color palette +- Background: `#070708` (`--bg`) +- Elevated: `#101012` +- Ivory text: `#f3eee6` (`--fg`) +- Muted: `rgba(243, 238, 230, 0.62)` +- Gold: `#c9a86a` (`--accent`) +- Hairline: `rgba(243, 238, 230, 0.14)` +- Flat: no shadows, square corners, 1px borders +- Header is fixed. Frosted over the home video; solid `#000` after scroll (`.header--solid`) +- Header tools: account menu, cart (badge + popup), phone, Instagram + +## Auth (customer dashboard) +- Do **not** add `/login`, `/register`, `/cart`, or `/checkout` on this site +- Anonymous account icon → `https://customer.ayoubi.jewelry` +- Logged in (cookie `meshkee_customer_access_token`, may be chunked): show `{firstName|nameFa|name} عزیز` +- Dropdown: `پروفایل من`, `خروج`, and if admin `مدیریت وبسایت` → `https://business.ayoubi.jewelry` +- Session: `src/lib/auth.ts` + `src/lib/authUser.ts` via `GET /auth/me` + +## Shopping cart (mini-cart only) +- Header cart icon + quantity badge + popup (`CartButton` + `CartProvider` + `guestCart.ts`) +- Persist `meshkee-guest-cart` in localStorage + cookie on `.ayoubi.jewelry` +- Add-to-cart only when `GET .../store-items/by-product/{productId}` returns in-stock variants +- Line `id` = `storeItemVariantId`; price = `discountedPrice ?? price` +- Continue: logged in → `customer.../checkout/cart?guestCart=…`; else login with that redirect +- Never call `/businesses/{id}/cart*` from this site + +## Pages +| Route | Purpose | +| --- | --- | +| `/` | Video hero, category studio, local/CMS gallery, follow, carousel, parallax, visit | +| `/products` | Catalog from Meshkee products API + jewelry category filters + pagination | +| `/products/[id]/[nameFaSlug]` | Product detail (canonical); redirect if slug drifts | +| `/products/category/[id]/[nameFaSlug]` | Category product list | +| `/portfolio` | Portfolio list (API; empty until CMS content) | +| `/portfolio/[slug]` | Portfolio detail: lightbox gallery, zoom, similar row | +| `/blog` | Blog list (API; empty until CMS content) | +| `/blog/[slug]` | Blog detail | +| `/about` | درباره گالری | +| `/contact` | Contact facts + form → `POST /api/contact` → Meshkee contact-submissions | + +## Product detail requirements +- Media gallery with hover zoom (desktop) + lightbox +- Tabs: توضیحات، مشخصات فنی (via `.../technical-info` join), دیدگاه‌ها +- Comments: form only when authenticated; else login link to customer portal +- Similar products from `relatedProducts` +- Emit `product.schema.jsonLd` when present + +## Homepage (visual gallery) +- Local images in `public/assets/images/` / `gallery` in `src/data/site.ts` as fallbacks +- CMS slots: `categories`, `four-banner`, `masonry-image`, `instagram-image`, `last-banner` (see Static image slots) +- Homepage masonry is atmospheric; purchasable catalog is `/products` + +## Brand copy (fallback) +- Name: گالری طلا و جواهر ایوبی فخیم / Ayoubi Fakhim Jewelry +- Tagline: این طلا همیشه مده +- Address: قم - سالاریه - اریکه ملل +- Phones: ۰۹۹۹ ۱۲۹ ۶۶۶۶ (`09991296666`), ۰۹۳۷ ۱۴۴ ۸۹۰۵ (`09371448905`) +- Instagram: https://www.instagram.com/ayoubifakhim/ +- Logo / favicon fallback: `public/Ayoobi-logo.png` + +## Meshkee helpers +- `src/lib/meshkee.ts` — business-info, favicon +- `src/lib/catalog.ts` — products, categories, blogs, portfolios, comments, store-items, `/auth/me` +- `src/lib/slug.ts` — canonical product/category/blog/portfolio hrefs +- Favicon: `GET /website/favicon` → `metadata.icons`, else `/Ayoobi-logo.png` + +### Static image slots +Catalog: `GET /meshkee/static-image-slots` from `STATIC_IMAGE_SLOTS` in `src/lib/static-images.ts`. +Fetch per page: `GET /tenants/{domain}/website/static-images?pageKey=…`. +Empty/fail → keep local placeholders. `url` → ``; `linkUrl` → wrap in ``. +Use `titleFa` / `titleEn` / `subtext` to match the original UI labels. +Apply `slot.aspectRatio` from the API (fallback to catalog). **If an on-page frame’s size or aspect ratio changes in development, update the matching `STATIC_IMAGE_SLOTS` entry in the same change.** + +| key | kind | itemCount | aspectRatio | width | UI | +| --- | --- | --- | --- | --- | --- | +| `categories` | list | null | `1:1` | 320 | Category row — `titleFa`/`titleEn` beside image (icon fallback) | +| `four-banner` | list | 4 | `3:4` | 1440 | Sticky “از فلز تا پوشیدن” — Fa/En/subtext + image | +| `masonry-image` | list | null | `3:4` | 960 | Masonry gallery (free display height) | +| `instagram-image` | single | 1 | `4:5` | 480 | Follow frame photo + optional Fa/En/subtext | +| `last-banner` | single | 1 | `16:9` | 1600 | Parallax end banner | +| `about-portrait` | single | 1 | `3:4` | 900 | About portrait | + +Helpers: `src/lib/home-slots.ts`, `SlotImage`. + +### Tags & badges +From `business-info`: `metaTags` as `{ html }` **or** `{ name, content }` → root `` via `CmsMeta`. `trustBadges[].embedHtml` → footer (scripts stripped). Empty → omit; do not hardcode eNamad/Samandehi. + +### SEO +- Do not ship local `sitemap.xml` / `robots.txt` / `app/sitemap.ts` / `app/robots.ts` +- Static pages: `GET /meshkee/sitemap-config.json` +- One H1 per page; content images have alt; use `seoMetaTitle` / `seoMetaDescription` when set +- Organization JSON-LD from business-info; product/blog detail emit their own `schema.jsonLd` + +## Important paths +- Layout / providers: `src/app/layout.tsx`, `src/components/providers/*` +- Header / footer: `src/components/Header.tsx`, `AccountMenu.tsx`, `CartButton.tsx`, `Footer.tsx` +- Catalog UI: `MediaGallery.tsx`, `ProductTabs.tsx`, `AddToCart.tsx`, `CatalogCard.tsx`, `CommentSection.tsx` +- Contact: `src/components/ContactForm.tsx` + toast, `src/app/api/contact/route.ts` diff --git a/public/assets/images/498025374_9837247912996594_6060652491414854135_n.jpg b/public/assets/images/498025374_9837247912996594_6060652491414854135_n.jpg new file mode 100644 index 0000000..ce65ce0 Binary files /dev/null and b/public/assets/images/498025374_9837247912996594_6060652491414854135_n.jpg differ diff --git a/public/assets/images/503741244_1401121107696246_2944516772511839745_n.jpg b/public/assets/images/503741244_1401121107696246_2944516772511839745_n.jpg new file mode 100644 index 0000000..d7ceaad Binary files /dev/null and b/public/assets/images/503741244_1401121107696246_2944516772511839745_n.jpg differ diff --git a/public/assets/images/541582664_18076098125511213_2119871898364747318_n.jpg b/public/assets/images/541582664_18076098125511213_2119871898364747318_n.jpg new file mode 100644 index 0000000..de128f3 Binary files /dev/null and b/public/assets/images/541582664_18076098125511213_2119871898364747318_n.jpg differ diff --git a/public/assets/images/541932677_18076083281511213_2998824066892089068_n.jpg b/public/assets/images/541932677_18076083281511213_2998824066892089068_n.jpg new file mode 100644 index 0000000..98e8e57 Binary files /dev/null and b/public/assets/images/541932677_18076083281511213_2998824066892089068_n.jpg differ diff --git a/public/assets/images/541987645_18076083023511213_4894611079046832481_n.jpg b/public/assets/images/541987645_18076083023511213_4894611079046832481_n.jpg new file mode 100644 index 0000000..c461476 Binary files /dev/null and b/public/assets/images/541987645_18076083023511213_4894611079046832481_n.jpg differ diff --git a/public/assets/images/761279832_18116759840511213_5427821256309530192_n.jpeg b/public/assets/images/761279832_18116759840511213_5427821256309530192_n.jpeg new file mode 100644 index 0000000..0679d53 Binary files /dev/null and b/public/assets/images/761279832_18116759840511213_5427821256309530192_n.jpeg differ diff --git a/public/assets/images/778082448_18119807777511213_8387248507204531029_n.jpg b/public/assets/images/778082448_18119807777511213_8387248507204531029_n.jpg new file mode 100644 index 0000000..fefc54d Binary files /dev/null and b/public/assets/images/778082448_18119807777511213_8387248507204531029_n.jpg differ diff --git a/public/assets/images/825269750_18124978682511213_3830069416169726999_n.jpg b/public/assets/images/825269750_18124978682511213_3830069416169726999_n.jpg new file mode 100644 index 0000000..ac81708 Binary files /dev/null and b/public/assets/images/825269750_18124978682511213_3830069416169726999_n.jpg differ diff --git a/public/assets/images/830137832_18125270558511213_7995126111595658575_n.jpg b/public/assets/images/830137832_18125270558511213_7995126111595658575_n.jpg new file mode 100644 index 0000000..1a8cfbc Binary files /dev/null and b/public/assets/images/830137832_18125270558511213_7995126111595658575_n.jpg differ diff --git a/public/assets/images/834767155_18125669717511213_5824315146052276957_n.jpg b/public/assets/images/834767155_18125669717511213_5824315146052276957_n.jpg new file mode 100644 index 0000000..533022e Binary files /dev/null and b/public/assets/images/834767155_18125669717511213_5824315146052276957_n.jpg differ diff --git a/public/assets/images/842977415_18126295883511213_701670290850293570_n.jpg b/public/assets/images/842977415_18126295883511213_701670290850293570_n.jpg new file mode 100644 index 0000000..fa0b7d5 Binary files /dev/null and b/public/assets/images/842977415_18126295883511213_701670290850293570_n.jpg differ diff --git a/public/assets/images/instagram.jpeg b/public/assets/images/instagram.jpeg new file mode 100644 index 0000000..41f9f84 Binary files /dev/null and b/public/assets/images/instagram.jpeg differ diff --git a/public/assets/video/MakeJewel.mp4 b/public/assets/video/MakeJewel.mp4 new file mode 100644 index 0000000..289efb3 Binary files /dev/null and b/public/assets/video/MakeJewel.mp4 differ diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..a75359b --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,80 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { gallery, site } from "@/data/site"; +import { resolveAbout, resolveAddress } from "@/lib/content"; +import { getBusinessInfo } from "@/lib/meshkee"; +import { getPageSlots, imagesForSlot } from "@/lib/static-images"; + +export const metadata: Metadata = { + title: "درباره ما", + description: + "آشنایی با گالری طلا و جواهر ایوبی فخیم در قم؛ طراحی و عرضه طلا، الماس و جواهر در سالاریه، مجتمع اریکه ملل.", + openGraph: { + title: "درباره گالری طلا و جواهر ایوبی فخیم", + description: + "آشنایی با گالری طلا و جواهر ایوبی فخیم در قم؛ طراحی و عرضه طلا، الماس و جواهر در سالاریه، مجتمع اریکه ملل.", + images: [{ url: gallery[0].src, alt: gallery[0].alt }], + }, +}; + +export default async function AboutPage() { + const [info, slots] = await Promise.all([getBusinessInfo(), getPageSlots("about")]); + const paragraphs = resolveAbout(info); + const portrait = imagesForSlot(slots, "about-portrait")[0]; + const image = { + src: portrait?.url || gallery[0].src, + alt: portrait?.titleFa || portrait?.titleEn || gallery[0].alt, + href: portrait?.linkUrl || undefined, + }; + const address = resolveAddress(info); + + return ( +
+
+

{site.shortFa}

+

درباره گالری

+

{paragraphs[0]}

+
+ +
+
+ {image.href ? ( + + {/* eslint-disable-next-line @next/next/no-img-element */} + {image.alt} + + ) : ( + // eslint-disable-next-line @next/next/no-img-element + {image.alt} + )} +
+
+

ساخت و انتخاب

+ {paragraphs.slice(1).map((paragraph) => ( +

{paragraph}

+ ))} + {info.vision?.trim() ?

{info.vision.trim()}

: null} +
+
+ +
+
+
+

نشانی گالری

+

{address}

+
+
+

ساعت حضور

+

برای بازدید هماهنگ کنید تا قطعه را آرام ببینید.

+
+
+ + تماس با گالری + +
+
+
+
+ ); +} diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts new file mode 100644 index 0000000..512031e --- /dev/null +++ b/src/app/api/contact/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; + +import { meshkeeApiBase, websiteDomain } from "@/lib/config"; + +export async function POST(request: Request) { + const body = (await request.json().catch(() => null)) as { + title?: string; + name?: string; + email?: string; + cellNumber?: string; + text?: string; + } | null; + + if (!body?.title?.trim() || !body.name?.trim() || !body.text?.trim()) { + return NextResponse.json({ message: "نام، موضوع و متن پیام الزامی است." }, { status: 400 }); + } + + const response = await fetch( + `${meshkeeApiBase}/tenants/${encodeURIComponent(websiteDomain)}/contact-submissions`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title: body.title.trim(), + name: body.name.trim(), + email: body.email?.trim() || undefined, + cellNumber: body.cellNumber?.trim() || undefined, + text: body.text.trim(), + }), + cache: "no-store", + }, + ); + + const data = await response.json().catch(() => ({})); + return NextResponse.json(data, { status: response.status }); +} diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..641af3d --- /dev/null +++ b/src/app/blog/[slug]/page.tsx @@ -0,0 +1,60 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; + +import { JsonLd } from "@/components/CmsMeta"; +import { asRichHtml, getBlogBySlug, safe } from "@/lib/catalog"; + +type PageProps = { params: Promise<{ slug: string }> }; + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + const data = await safe(getBlogBySlug(slug), null); + if (!data) return { title: "نوشته پیدا نشد" }; + const title = data.blog.seoMetaTitle || data.blog.title || data.blog.nameFa || slug; + const description = data.blog.seoMetaDescription || data.blog.summary || title; + const image = data.blog.thumbnailUrl || data.blog.image; + return { + title, + description, + openGraph: image ? { title, description, images: [{ url: image, alt: title }] } : { title, description }, + }; +} + +export default async function BlogDetailPage({ params }: PageProps) { + const { slug } = await params; + const data = await safe(getBlogBySlug(slug), null); + if (!data) notFound(); + + const blog = data.blog; + const title = blog.title || blog.nameFa || slug; + const html = asRichHtml(blog.contentHtml || blog.descriptionHtml); + const image = blog.thumbnailUrl || blog.image; + + return ( +
+ +
+
+ {blog.categoryNameFa ?

{blog.categoryNameFa}

: null} +

{title}

+ {blog.summary ?

{blog.summary}

: null} +
+ {image ? ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {title} +
+ ) : null} + {html ? ( +
+ ) : ( +

متن این نوشته هنوز تکمیل نشده است.

+ )} + + بازگشت به مجله + +
+
+ ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..24c0eef --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,70 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { CatalogCard } from "@/components/CatalogCard"; +import { getBlogs, safe } from "@/lib/catalog"; +import { toFaDigits } from "@/lib/format"; +import { getPageSlots } from "@/lib/static-images"; +import { blogHref } from "@/lib/slug"; + +export const metadata: Metadata = { + title: "مجله", + description: "نوشته‌ها و مطالب گالری طلا و جواهر ایوبی فخیم.", +}; + +export default async function BlogPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const { page: pageRaw } = await searchParams; + const page = Math.max(1, Number(pageRaw) || 1); + const pageSize = 12; + + const [result] = await Promise.all([ + safe(getBlogs({ page, pageSize }), { items: [], total: 0, page, pageSize }), + getPageSlots("blog"), + ]); + + const totalPages = Math.max(1, Math.ceil(result.total / pageSize)); + + return ( +
+
+

گالری ایوبی فخیم

+

مجله

+

نوشته‌ها و خبرهای گالری.

+
+ +
+ {result.items.length ? ( +
    + {result.items.map((blog) => ( +
  • + +
  • + ))} +
+ ) : ( +

هنوز نوشته‌ای منتشر نشده است.

+ )} + + {totalPages > 1 && result.items.length ? ( + + ) : null} +
+
+ ); +} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx new file mode 100644 index 0000000..9c54d45 --- /dev/null +++ b/src/app/contact/page.tsx @@ -0,0 +1,89 @@ +import type { Metadata } from "next"; + +import { ContactForm } from "@/components/ContactForm"; +import { site } from "@/data/site"; +import { resolveAddress, resolveEmails, resolveInstagram, resolvePhones } from "@/lib/content"; +import { getBusinessInfo } from "@/lib/meshkee"; +import { getPageSlots } from "@/lib/static-images"; + +export const metadata: Metadata = { + title: "تماس با ما", + description: + "تماس با گالری طلا و جواهر ایوبی فخیم؛ آدرس قم، سالاریه، اریکه ملل و شماره‌های ۰۹۹۹۱۲۹۶۶۶۶ و ۰۹۳۷۱۴۴۸۹۰۵.", + openGraph: { + title: "تماس با گالری طلا و جواهر ایوبی فخیم", + description: + "تماس با گالری طلا و جواهر ایوبی فخیم؛ آدرس قم، سالاریه، اریکه ملل و شماره‌های ۰۹۹۹۱۲۹۶۶۶۶ و ۰۹۳۷۱۴۴۸۹۰۵.", + }, +}; + +export default async function ContactPage() { + const [info] = await Promise.all([getBusinessInfo(), getPageSlots("contact")]); + const address = resolveAddress(info); + const phones = resolvePhones(info); + const emails = resolveEmails(info); + const instagram = resolveInstagram(info); + const mapHref = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(address)}`; + + return ( +
+
+

قم، سالاریه

+

تماس با ما

+

برای بازدید، سفارش یا پرسش درباره یک قطعه پیام بگذارید یا تماس بگیرید.

+
+ +
+
+

راه‌های ارتباط

+
+
+
آدرس
+
{address}
+
+
+
تلفن
+
+ {phones.map((phone) => ( + + {phone.display} + + ))} +
+
+ {emails.length ? ( +
+
ایمیل
+
+ {emails.map((email) => ( + + {email} + + ))} +
+
+ ) : null} +
+
اینستاگرام
+
+ + {instagram.handle} + +
+
+
+ + مسیر روی نقشه + +
+ +
+

پیام به گالری

+ +
+
+ +

{site.nameFa}

+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 575da97..84e0eb9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,8 +4,9 @@ --fg: #f3eee6; --fg-muted: rgba(243, 238, 230, 0.62); --accent: #c9a86a; - --accent-soft: rgba(201, 168, 106, 0.22); - --line: rgba(243, 238, 230, 0.12); + --accent-soft: rgba(201, 168, 106, 0.35); + --line: rgba(243, 238, 230, 0.14); + --header-h: 5.25rem; --ease: cubic-bezier(0.22, 1, 0.36, 1); } @@ -18,21 +19,21 @@ html, body { margin: 0; - height: 100%; - min-height: 100%; background: var(--bg); color: var(--fg); } -body { - font-family: var(--font-doran), system-ui, sans-serif; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; +html { + color-scheme: dark; } -a { - color: inherit; - text-decoration: none; +body { + min-height: 100dvh; + font-family: var(--font-doran), system-ui, sans-serif; + font-weight: 400; + line-height: 1.8; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } img, @@ -42,205 +43,1000 @@ video { max-width: 100%; } -.coming-soon { - position: relative; - display: grid; - grid-template-columns: 1fr 1fr; - align-items: stretch; - height: 100dvh; - min-height: 100dvh; - overflow: hidden; - background: - radial-gradient(ellipse 70% 50% at 20% 40%, rgba(201, 168, 106, 0.08), transparent 55%), - radial-gradient(ellipse 50% 40% at 80% 80%, rgba(255, 255, 255, 0.03), transparent 50%), - var(--bg); +a { + color: inherit; + text-decoration: none; } -.coming-soon__panel { - position: relative; - z-index: 1; +button, +input, +textarea { + font: inherit; + color: inherit; +} + +:focus-visible { + outline: 1px solid var(--accent); + outline-offset: 3px; +} + +.wrap { + width: min(1120px, calc(100% - 2.5rem)); + margin-inline: auto; +} + +.header { + position: fixed; + inset-inline: 0; + top: 0; + z-index: 20; + background: rgba(0, 0, 0, 0.28); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + border-bottom: 1px solid rgba(243, 238, 230, 0.08); + transition: + background-color 0.35s var(--ease), + border-color 0.35s var(--ease), + backdrop-filter 0.35s var(--ease); +} + +.header.is-open, +.header:has(.mega.is-open) { + background: #000; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border-bottom-color: var(--line); +} + +.header--solid { + background: #000; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border-bottom-color: var(--line); +} + +.header__bar { + min-height: var(--header-h); display: flex; align-items: center; + gap: 1.5rem; +} + +.brand { + display: flex; + align-items: center; + gap: 0.75rem; + min-width: 0; +} + +.brand__logo, +.footer__logo { + width: 3.4rem; + height: 3.4rem; + object-fit: contain; +} + +.brand__names { + display: flex; + flex-direction: column; + gap: 0.1rem; + line-height: 1.35; +} + +.brand__en, +.footer__en { + font-family: var(--font-cinzel), Georgia, serif; + font-size: 0.78rem; + letter-spacing: 0.08em; +} + +.brand__fa, +.footer__name { + color: var(--fg-muted); + font-size: 0.92rem; +} + +.header__end { + display: flex; + align-items: center; + align-self: stretch; + gap: 1.15rem; + margin-inline-start: auto; +} + +.nav { + display: flex; + align-items: stretch; + align-self: stretch; + gap: 1.35rem; + font-size: 0.98rem; +} + +.nav > a { + display: inline-flex; + align-items: center; +} + +.header__tools { + display: flex; + align-items: center; + gap: 0.15rem; +} + +.header__icon { + display: inline-flex; + align-items: center; justify-content: center; + width: 2rem; + height: 2rem; + color: var(--fg); +} + +.header__icon svg { + width: 1.2rem; + height: 1.2rem; + fill: none; + stroke: currentColor; + stroke-width: 1.5; + stroke-linecap: round; + stroke-linejoin: round; +} + +.header__icon:hover { + color: var(--accent); +} + +.nav a[aria-current="page"] { + color: var(--accent); +} + +.mega { + position: static; + display: flex; + align-items: center; +} + +.mega__trigger { + display: inline-flex; + align-items: center; align-self: stretch; - height: 100%; - min-height: 100dvh; - padding: clamp(2rem, 5vw, 4.5rem); + gap: 0.3rem; + border: 0; + background: transparent; + padding: 0; + color: inherit; + font: inherit; + cursor: pointer; } -.coming-soon__content { - width: min(100%, 34rem); +.mega__trigger[aria-current="page"], +.mega.is-open .mega__trigger, +.mega__trigger:hover { + color: var(--accent); +} + +.mega__chev { + font-size: 0.7rem; + line-height: 1; + opacity: 0.75; +} + +.mega__panel { + position: absolute; + inset-inline: 0; + top: 100%; + z-index: 25; + border-bottom: 1px solid var(--line); + background: #000; + padding: 1.5rem 0 1.35rem; +} + +.mega__panel::before { + content: ""; + position: absolute; + inset-inline: 0; + bottom: 100%; + height: 0.85rem; +} + +.mega__panel[hidden] { + display: none; +} + +.mega__inner { display: flex; flex-direction: column; - align-items: center; - text-align: center; - gap: 1.75rem; - margin-block: auto; + gap: 1.15rem; } -.coming-soon__logo { - width: clamp(11rem, 24vw, 15rem); - height: auto; - object-fit: contain; - margin-inline: auto; - opacity: 0; - animation: fade-up 1s var(--ease) 0.1s forwards; +.mega__groups { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); + gap: 1.5rem 2rem; } -.coming-soon__brand { +.mega__groups > li { display: flex; flex-direction: column; - align-items: center; - gap: 0.65rem; - opacity: 0; - animation: fade-up 1s var(--ease) 0.28s forwards; + gap: 0.75rem; + align-items: stretch; } -.coming-soon__name { - margin: 0; - font-family: var(--font-cinzel), Georgia, serif; - font-size: clamp(1.35rem, 2.4vw, 1.85rem); - font-weight: 500; - letter-spacing: 0.06em; - line-height: 1.2; +.mega__groups:has(> li:only-child) { + grid-template-columns: 1fr; } -.coming-soon__name-fa { +.mega__l1 { + color: var(--accent); + font-size: 1rem; +} + +.mega__l2 { + list-style: none; margin: 0; - font-family: var(--font-doran), system-ui, sans-serif; - font-size: clamp(1.05rem, 1.8vw, 1.25rem); - font-weight: 400; + padding: 0; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.55rem 1.25rem; +} + +.mega__groups:not(:has(> li:only-child)) .mega__l2 { + grid-template-columns: 1fr; +} + +.mega__l2 a { color: var(--fg-muted); - letter-spacing: 0; - line-height: 1.7; + font-size: 0.95rem; } -.coming-soon__tagline { +.mega__l2 a:hover, +.mega__l1:hover, +.mega__all:hover { + color: var(--accent); +} + +.mega__all { + width: fit-content; + color: var(--accent); + border-bottom: 1px solid var(--accent-soft); + font-size: 0.92rem; +} + +.mega__empty { margin: 0; + color: var(--fg-muted); + font-size: 0.92rem; +} + +.nav a:hover, +.footer a:hover, +.visit a:hover, +.facts a:hover { + color: var(--accent); +} + +.hero { + position: relative; + height: 100dvh; + min-height: 22rem; + background: #000; + overflow: hidden; +} + +.hero__video { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; +} + +.hero__brand { + position: absolute; + left: 50%; + bottom: 10%; + z-index: 2; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 0.45rem; + width: max-content; + max-width: calc(100% - 2.5rem); + padding: 1.5rem 0.5rem; + text-align: center; + background: transparent; + border: 0; +} + +.hero__brand::before { + content: ""; + position: absolute; + z-index: -1; + inset: -1.5rem -3.5rem; + background: rgba(0, 0, 0, 0.22); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + mask-image: radial-gradient(ellipse at center, #000 28%, transparent 72%); + -webkit-mask-image: radial-gradient(ellipse at center, #000 28%, transparent 72%); + pointer-events: none; +} + +.hero__en, +.hero__fa { + margin: 0; +} + +.hero__fa { + font-size: clamp(1.7rem, 3.4vw, 2.65rem); + font-weight: 500; + line-height: 1.45; +} + +.hero__en { + font-family: var(--font-cinzel), Georgia, serif; + font-size: clamp(0.95rem, 1.7vw, 1.2rem); + letter-spacing: 0.14em; + color: var(--fg); +} + +.intro { + padding: clamp(3.5rem, 8vw, 6rem) 0 clamp(2rem, 5vw, 3.5rem); + text-align: center; +} + +.categories { + padding: 3.5rem 0 3rem; +} + +.categories__inner { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.15rem; + text-align: center; +} + +.category-grid { + list-style: none; + width: 100%; + margin: 1.75rem 0 0; + padding: 0; + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 0.85rem; +} + +.category-frame { + width: 100%; + min-height: 13.5rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.45rem; + padding: 1.25rem 0.4rem; + border: 1px solid transparent; + background: transparent; + color: var(--fg); + cursor: pointer; + transition: border-color 0.35s var(--ease); +} + +.category-frame svg, +.category-frame__media { + width: 6rem; + height: 6rem; + margin-bottom: 0.45rem; +} + +.category-frame svg { + fill: none; + stroke: currentColor; + stroke-width: 1.25; + stroke-linecap: round; + stroke-linejoin: round; + color: var(--accent); +} + +.category-frame__media { + object-fit: contain; +} + +a.category-frame { + text-decoration: none; +} + +.slot-link { + display: block; + color: inherit; +} + +.sticky-story__media { + display: block; + position: absolute; + inset: 0; +} + +.sticky-story__media img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.parallax__link { + position: absolute; + inset: 0; + display: block; +} + +.category-frame__fa { + font-size: 1.05rem; + line-height: 1.4; +} + +.category-frame__en { + font-family: var(--font-cinzel), Georgia, serif; + font-size: 0.72rem; + letter-spacing: 0.12em; + line-height: 1.3; + color: var(--fg-muted); +} + +.category-frame:hover, +.category-frame[aria-pressed="true"] { + border-color: var(--accent); +} + +.category-empty { + margin: 0; + color: var(--fg-muted); +} + +.intro__inner { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.85rem; +} + +.intro h1, +.page-intro h1, +.categories h1, +.product h1 { + margin: 0; + font-size: clamp(1.7rem, 3.4vw, 2.55rem); + font-weight: 500; + line-height: 1.45; +} + +.kicker { display: inline-flex; align-items: center; justify-content: center; gap: 0.85rem; + margin: 0; color: var(--accent); font-family: var(--font-doran), system-ui, sans-serif; font-size: 0.95rem; - font-weight: 500; - letter-spacing: 0.12em; - opacity: 0; - animation: fade-up 1s var(--ease) 0.46s forwards; + letter-spacing: 0.04em; } -.coming-soon__tagline::before, -.coming-soon__tagline::after { +.kicker[dir="ltr"] { + font-family: var(--font-cinzel), Georgia, serif; + font-size: 0.82rem; + letter-spacing: 0.14em; +} + +.kicker::before, +.kicker::after { content: ""; width: 2rem; height: 1px; background: var(--accent-soft); } -.coming-soon__meta { - display: flex; - flex-direction: column; - align-items: center; - gap: 1.1rem; - width: 100%; - padding-top: 0.35rem; - border-top: 1px solid var(--line); - opacity: 0; - animation: fade-up 1s var(--ease) 0.64s forwards; +.tagline { + margin: 0.2rem 0 0; + color: var(--accent); + letter-spacing: 0.08em; } -.coming-soon__label { - display: block; - margin-bottom: 0.35rem; - font-family: var(--font-doran), system-ui, sans-serif; - font-size: 0.8rem; - letter-spacing: 0.08em; +.lead { + max-width: 38rem; + margin: 0.35rem 0 0; color: var(--fg-muted); } -.coming-soon__phone, -.coming-soon__social, -.coming-soon__address { - font-size: 1.05rem; - font-weight: 400; - letter-spacing: 0.04em; - transition: color 0.3s var(--ease); +.section { + padding: 1.5rem 0 4.5rem; } -.coming-soon__address { +.about h2, +.contact h2, +.visit h2, +.related h2 { margin: 0; - font-family: var(--font-doran), system-ui, sans-serif; - line-height: 1.7; - letter-spacing: 0; + font-size: 1.35rem; + font-weight: 500; } -.coming-soon__phone, -.coming-soon__social { - font-family: var(--font-cinzel), Georgia, serif; -} - -.coming-soon__phone { - font-family: var(--font-doran), system-ui, sans-serif; - letter-spacing: 0.06em; -} - -.coming-soon__phone:hover, -.coming-soon__social:hover { +.visit__label, +.footer__label, +.piece__category, +.product-card__category, +.facts dt { + margin: 0; color: var(--accent); + font-size: 0.86rem; } -.coming-soon__phones, -.coming-soon__socials { +.gallery { + list-style: none; + margin: 0; + padding: 0; + columns: 3; + column-gap: 1.15rem; +} + +.gallery > li { + break-inside: avoid; + margin-bottom: 1.5rem; +} + +.piece { + position: relative; + margin: 0; +} + +.piece__frame { + display: block; + width: 100%; + height: auto; + padding: 0; + border: 0; + background: transparent; + cursor: pointer; +} + +a.piece__frame { + cursor: pointer; +} + +.piece__frame img { + width: 100%; + height: auto; +} + +.piece figcaption { + position: absolute; + inset: auto 0 0; display: flex; flex-direction: column; + gap: 0.15rem; + margin: 0; + padding: 2.75rem 0.95rem 0.9rem; + background: linear-gradient(to top, rgba(0, 0, 0, 0.78), transparent); + opacity: 0; + transition: opacity 0.35s var(--ease); + pointer-events: none; +} + +.piece:hover figcaption, +.piece:focus-within figcaption { + opacity: 1; +} + +.piece__title { + font-size: 1.02rem; +} + +.lightbox { + width: min(92vw, 38rem); + max-height: 92vh; + padding: 1rem; + border: 1px solid var(--line); + background: var(--bg); + color: var(--fg); +} + +.lightbox::backdrop { + background: rgba(0, 0, 0, 0.88); +} + +.lightbox__body { + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.lightbox__close { + align-self: flex-start; + border: 1px solid var(--line); + background: transparent; + padding: 0.35rem 0.8rem; + cursor: pointer; +} + +.lightbox img { + width: 100%; + max-height: 70vh; + object-fit: contain; + background: #0c0c0e; +} + +.lightbox p { + display: flex; + flex-direction: column; + gap: 0.15rem; + margin: 0; +} + +.lightbox p span { + color: var(--accent); + font-size: 0.86rem; +} + +.follow { + border-top: 1px solid var(--line); + padding: 3.5rem 0; +} + +.follow__grid { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); + direction: ltr; align-items: center; + gap: 2.5rem; +} + +.follow__copy { + direction: rtl; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; +} + +.follow__copy h2 { + margin: 0.85rem 0 0; + font-size: clamp(1.45rem, 2.4vw, 2rem); + font-weight: 500; + line-height: 1.5; +} + +.follow__photo { + position: relative; + justify-self: start; + width: min(100%, 18.5rem); + margin: 1.25rem 1.25rem 1.25rem 0; +} + +.follow__photo::before { + content: ""; + position: absolute; + z-index: 0; + top: 1.15rem; + right: -1.15rem; + bottom: -1.15rem; + left: 1.15rem; + border: 1px solid var(--accent); +} + +.follow__photo img { + position: relative; + z-index: 1; + width: 100%; + height: auto; +} + +.carousel { + border-top: 1px solid var(--line); + padding: 3rem 0 3.5rem; +} + +.carousel__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.4rem; +} + +.carousel__head h2 { + margin: 0; + font-size: 1.35rem; + font-weight: 500; +} + +.carousel__nav { + display: flex; gap: 0.45rem; } -.coming-soon__meta > div { +.carousel__nav button { + display: grid; + place-items: center; + width: 2.4rem; + height: 2.4rem; + padding: 0; + border: 1px solid var(--line); + background: transparent; + color: var(--fg); + cursor: pointer; + transition: border-color 0.35s var(--ease), color 0.35s var(--ease); +} + +.carousel__nav button:hover:not(:disabled) { + border-color: var(--accent); + color: var(--accent); +} + +.carousel__nav button:disabled { + opacity: 0.35; + cursor: default; +} + +.carousel__nav svg { + width: 1rem; + height: 1rem; + fill: none; + stroke: currentColor; + stroke-width: 1.5; + stroke-linecap: round; + stroke-linejoin: round; +} + +.carousel__track { + display: flex; + gap: 1rem; + margin: 0; + padding: 0 0 0.2rem; + list-style: none; + overflow-x: auto; + scroll-snap-type: x mandatory; + scrollbar-width: none; +} + +.carousel__track::-webkit-scrollbar { + display: none; +} + +.carousel__track > li { + flex: 0 0 calc((100% - 4rem) / 4.5); + scroll-snap-align: start; +} + +.carousel__card { + margin: 0; +} + +.carousel__frame { + display: block; width: 100%; -} - -.coming-soon__media { - position: relative; - min-height: 100dvh; - background: #050505; + aspect-ratio: 3 / 4; + padding: 0; + border: 1px solid transparent; + background: #0c0c0e; overflow: hidden; - opacity: 0; - animation: fade-in 1.4s var(--ease) 0.2s forwards; + cursor: pointer; + transition: border-color 0.35s var(--ease); } -.coming-soon__media::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - background: - linear-gradient(90deg, rgba(7, 7, 8, 0.55), transparent 28%), - linear-gradient(0deg, rgba(7, 7, 8, 0.35), transparent 30%); +.carousel__frame:hover { + border-color: var(--accent); } -.coming-soon__video { +.carousel__frame img { width: 100%; height: 100%; - min-height: 100dvh; object-fit: cover; - object-position: center center; } -@keyframes fade-up { - from { - opacity: 0; - transform: translateY(18px); - } - to { - opacity: 1; - transform: translateY(0); - } +.carousel__card figcaption { + display: flex; + flex-direction: column; + gap: 0.15rem; + padding-top: 0.7rem; } -@keyframes fade-in { +.parallax { + --parallax-y: 0px; + position: relative; + height: min(72vh, 38rem); + min-height: 24rem; + overflow: hidden; + border-top: 1px solid var(--line); + background: #000; +} + +.parallax__media { + position: absolute; + left: 0; + top: -18%; + width: 100%; + height: 136%; + object-fit: cover; + object-position: center 42%; + transform: translate3d(0, var(--parallax-y), 0); +} + +.parallax__shade { + position: absolute; + inset: 0; + background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.62) 78%); + pointer-events: none; +} + +.parallax__plate { + position: absolute; + inset: 1.6rem; + border: 1px solid var(--accent); + pointer-events: none; + transform: translate3d(0, calc(var(--parallax-y) * -0.4), 0); +} + +.parallax__copy { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + pointer-events: none; + align-items: center; + justify-content: center; + gap: 0.9rem; + height: 100%; + padding: 2rem; + text-align: center; + transform: translate3d(0, calc(var(--parallax-y) * -0.65), 0); +} + +.parallax__copy h2 { + margin: 0; + font-size: clamp(1.8rem, 4vw, 3rem); + font-weight: 500; + line-height: 1.45; +} + +.sticky-story { + --story: 0; + --frame-inset: 1.6rem; + height: calc((100dvh - var(--header-h)) * 4); + background: #000; + border-top: 1px solid var(--line); +} + +.sticky-story__stage { + position: sticky; + top: var(--header-h); + height: calc(100dvh - var(--header-h)); + overflow: hidden; + background: #000; +} + +.sticky-story__slide { + position: absolute; + inset: 0; + margin: 0; + min-height: calc(100dvh - var(--header-h)); + opacity: 0; +} + +.sticky-story__slide.is-under { + opacity: 1; + z-index: 0; +} + +.sticky-story__slide.is-under .sticky-story__copy { + opacity: 0; +} + +.sticky-story__slide.is-on { + z-index: 1; + opacity: 1; + animation: story-fade 0.8s var(--ease); +} + +.sticky-story__slide img, +.sticky-story__shade, +.sticky-story__copy { + position: absolute; + inset: 0; +} + +.sticky-story__slide img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.sticky-story__shade { + background: + linear-gradient(to left, rgba(7, 7, 8, 0.84) 0%, rgba(7, 7, 8, 0.42) 34%, rgba(7, 7, 8, 0.08) 62%), + linear-gradient(to right, rgba(7, 7, 8, 0.45), transparent 28%); + pointer-events: none; +} + +.sticky-story__plate { + position: absolute; + z-index: 2; + inset: var(--frame-inset); + border: 1px solid var(--accent); + pointer-events: none; +} + +.sticky-story__copy { + z-index: 1; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + pointer-events: none; + gap: 0.85rem; + max-width: 38rem; + padding: 2rem clamp(2rem, 7vw, 6rem); + text-align: start; +} + +.sticky-story__copy h2 { + margin: 0; + font-size: clamp(2.1rem, 5vw, 3.5rem); + font-weight: 500; + line-height: 1.3; +} + +.sticky-story__copy .lead { + max-width: 24rem; + color: rgba(243, 238, 230, 0.86); +} + +.sticky-story__rail { + position: absolute; + z-index: 2; + top: 50%; + inset-inline-end: calc(var(--frame-inset) + 1.15rem); + transform: translateY(-50%); + pointer-events: none; + display: flex; + flex-direction: column; + gap: 0.9rem; + margin: 0; + padding: 0; + list-style: none; +} + +.sticky-story__rail li { + font-family: var(--font-cinzel), Georgia, serif; + font-size: 0.72rem; + letter-spacing: 0.16em; + color: rgba(243, 238, 230, 0.55); +} + +.sticky-story__rail li.is-on { + color: var(--accent); +} + +.sticky-story__meter { + position: absolute; + z-index: 2; + inset-inline: calc(var(--frame-inset) + 1.4rem); + bottom: calc(var(--frame-inset) + 0.85rem); + height: 1px; + pointer-events: none; + background: rgba(243, 238, 230, 0.28); +} + +.sticky-story__meter span { + display: block; + height: 100%; + background: var(--accent); + transform: scaleX(var(--story)); + transform-origin: 100% 50%; +} + +@keyframes story-fade { from { opacity: 0; } @@ -249,51 +1045,1056 @@ video { } } +.visit { + border-top: 1px solid var(--line); + padding: 3rem 0; +} + +.visit__grid { + display: grid; + grid-template-columns: 1.4fr 1fr 1fr; + gap: 2rem; + align-items: start; +} + +.visit p, +.about p, +.contact p, +.product p, +.facts dd { + margin: 0.35rem 0 0; +} + +.visit__phones, +.facts dd, +.footer__links { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; +} + +.page { + padding-top: var(--header-h); + padding-bottom: 3rem; +} + +.page-intro { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.8rem; + padding-top: clamp(2.5rem, 6vw, 4.5rem); + padding-bottom: 2rem; +} + +.about, +.contact, +.product { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2.5rem; + align-items: center; + padding-bottom: 3rem; +} + +.product { + padding-top: clamp(2.5rem, 6vw, 4.5rem); +} + +.about__figure, +.product__figure { + margin: 0; + border: 1px solid var(--line); + line-height: 0; + overflow: hidden; +} + +.about__figure img, +.product__figure img { + display: block; + width: 100%; + height: auto; +} + +.product__actions { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.35rem; + margin-top: 1.25rem; +} + +.catalog { + padding-bottom: 1rem; +} + +.filters { + display: flex; + flex-wrap: wrap; + gap: 0.35rem 1.35rem; + margin: 0 0 1.75rem; + padding: 0; + list-style: none; +} + +.filters button, +.filters a { + border: 0; + border-bottom: 1px solid transparent; + background: transparent; + padding: 0.15rem 0 0.25rem; + color: var(--fg-muted); + cursor: pointer; +} + +.filters button[aria-pressed="true"], +.filters button:hover, +.filters a[aria-current="page"], +.filters a:hover { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.product-grid, +.related__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.75rem; + margin: 0; + padding: 0; + list-style: none; +} + +.product-card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.35rem; + height: 100%; + padding: 0.5rem 0.5rem 0.7rem; + border: 1px solid var(--line); + border-radius: 0.45rem; + transition: border-color 0.25s var(--ease); +} + +.product-card:hover { + border-color: var(--accent); +} + +.product-card__frame { + display: block; + width: 100%; + margin-bottom: 0.35rem; + border-radius: 0.3rem; + line-height: 0; + overflow: hidden; +} + +.product-card__frame img { + display: block; + width: 100%; + height: auto; +} + +.product-card__title { + font-size: 1.05rem; +} + +.product-card__summary { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + color: var(--fg-muted); + font-family: var(--font-iranyekan), Tahoma, sans-serif; + font-size: 0.9rem; + font-weight: 300; + line-height: 1.6; +} + +.related { + border-top: 1px solid var(--line); + margin-top: 0.5rem; + padding-top: 2.25rem; + padding-bottom: 1rem; +} + +.catalog-more { + margin: 1.25rem 0 0; +} + +.about p + p, +.product p + p { + margin-top: 1rem; +} + +.facts { + margin: 1rem 0 1.25rem; +} + +.facts div + div { + margin-top: 1rem; +} + +.facts dt { + margin-bottom: 0.2rem; +} + +.form { + display: flex; + flex-direction: column; + gap: 0.9rem; + margin-top: 1rem; +} + +.form__row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.9rem; +} + +.form label { + display: flex; + flex-direction: column; + gap: 0.4rem; + color: var(--fg-muted); + font-size: 0.92rem; +} + +.form input, +.form textarea { + width: 100%; + border: 1px solid var(--line); + background: transparent; + padding: 0.75rem 0.9rem; +} + +.form input:focus, +.form textarea:focus { + border-color: var(--accent); +} + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + width: fit-content; + min-height: 2.8rem; + padding: 0.65rem 1.35rem; + border: 1px solid var(--accent); + background: transparent; + color: var(--accent); + cursor: pointer; +} + +.button:hover { + background: var(--accent); + color: #16140f; +} + +.button:disabled { + opacity: 0.6; + cursor: progress; +} + +.text-link { + display: inline-block; + margin-top: 0.8rem; + color: var(--accent); + border-bottom: 1px solid var(--accent-soft); +} + +.form__note { + margin: 0; + color: var(--accent); +} + +.form__note--error { + color: #e7b4b4; +} + +.contact__note { + color: var(--fg-muted); + padding-bottom: 1rem; +} + +.footer { + border-top: 1px solid var(--line); + padding: 2.5rem 0 1.5rem; +} + +.footer__grid { + display: grid; + grid-template-columns: 1.4fr 1fr 1fr auto; + gap: 1.5rem 2rem; +} + +.footer__name, +.footer__en, +.footer__text { + margin: 0.2rem 0 0; +} + +.footer__badges { + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding-top: 1.5rem; +} + +.footer__base { + margin-top: 1.75rem; + padding-top: 1rem; + border-top: 1px solid var(--line); + color: var(--fg-muted); + font-size: 0.9rem; +} + +.footer__base p { + margin: 0; +} + +@media (prefers-reduced-motion: no-preference) { + html.seq .seq-ready:not(.seq-in) { + opacity: 0; + translate: 0 18px; + } + + html.seq .seq-ready.seq-still:not(.seq-in) { + translate: none; + } + + html.seq .seq-ready.seq-in { + opacity: 1; + translate: none; + transition: + opacity 0.9s var(--ease), + translate 0.9s var(--ease); + transition-delay: calc(var(--seq-i, 0) * 70ms); + } + + html.seq .seq-ready.seq-still.seq-in { + translate: none; + } +} + @media (max-width: 900px) { - .coming-soon { - height: auto; - min-height: 100dvh; - grid-template-columns: 1fr; - grid-template-rows: minmax(48dvh, 56dvh) auto; + .gallery { + columns: 2; } - .coming-soon__media { - order: -1; - min-height: 0; + .carousel__track > li { + flex-basis: calc((100% - 2rem) / 2.5); } - .coming-soon__video { - min-height: 48dvh; - height: 100%; + .visit__grid, + .about, + .contact, + .product, + .footer__grid, + .product-grid, + .related__grid { + grid-template-columns: 1fr 1fr; } - .coming-soon__media::after { - background: - linear-gradient(180deg, transparent 55%, rgba(7, 7, 8, 0.92)), - linear-gradient(0deg, rgba(7, 7, 8, 0.2), transparent 35%); + .footer__grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 720px) { + :root { + --header-h: 8.75rem; } - .coming-soon__panel { - min-height: auto; - height: auto; - padding: 2.25rem 1.5rem 3rem; - align-items: center; + .brand__en { + display: none; } - .coming-soon__content { + .header__bar { + align-items: flex-start; + flex-wrap: wrap; + padding-block: 0.8rem; + } + + .header__end { width: 100%; - margin-block: 0; + margin-inline-start: 0; + gap: 0.75rem; + } + + .nav { + flex: 1; + gap: 1rem; + padding-bottom: 0.35rem; + overflow-x: auto; + } + + .mega__panel { + max-height: min(70vh, 28rem); + overflow: auto; + } + + .mega__groups { + grid-template-columns: 1fr; + gap: 1rem; + } + + .mega__l2 { + grid-template-columns: 1fr 1fr; + } + + .hero { + height: 100dvh; + } + + .gallery { + columns: 1; + } + + .follow__grid, + .visit__grid, + .about, + .contact, + .product, + .form__row, + .footer__grid { + grid-template-columns: 1fr; + } + + .product-grid, + .related__grid { + grid-template-columns: 1fr 1fr; + gap: 0.55rem; + } + + .category-grid { + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 0.4rem; + } + + .category-frame { + min-height: 8.5rem; + padding: 0.65rem 0.15rem; + } + + .category-frame svg { + width: 3.1rem; + height: 3.1rem; + } + + .category-frame__fa { + font-size: 0.78rem; + } + + .category-frame__en { + font-size: 0.52rem; + letter-spacing: 0.04em; + } + + .carousel__track > li { + flex-basis: calc((100% - 1rem) / 1.5); + } + + .parallax__plate { + inset: 0.75rem; + } + + .sticky-story { + --frame-inset: 0.75rem; + } + + .sticky-story__shade { + background: linear-gradient( + to top, + rgba(7, 7, 8, 0.9) 0%, + rgba(7, 7, 8, 0.55) 34%, + rgba(7, 7, 8, 0.08) 62% + ); + } + + .sticky-story__copy { + justify-content: flex-end; + max-width: none; + padding: 1.25rem 1.25rem 2.8rem; + } + + .sticky-story__copy h2 { + font-size: 1.9rem; + } + + .sticky-story__rail { + top: 1.15rem; + bottom: auto; + inset-inline-end: 1.15rem; + transform: none; + flex-direction: row; + gap: 0.75rem; } } @media (prefers-reduced-motion: reduce) { - .coming-soon__logo, - .coming-soon__brand, - .coming-soon__tagline, - .coming-soon__meta, - .coming-soon__media { + .hero { animation: none; - opacity: 1; + } + + .parallax__media, + .parallax__plate, + .parallax__copy { transform: none; } + + .sticky-story { + height: auto; + } + + .sticky-story__stage { + position: relative; + top: auto; + height: auto; + overflow: visible; + } + + .sticky-story__slide, + .sticky-story__slide.is-on, + .sticky-story__slide.is-under { + position: relative; + opacity: 1; + animation: none; + } + + .sticky-story__slide.is-under .sticky-story__copy { + opacity: 1; + } + + .sticky-story__rail, + .sticky-story__meter { + display: none; + } +} + +.account { + position: relative; +} + +.account__trigger { + display: inline-flex; + align-items: center; + gap: 0.35rem; + min-height: 2rem; + border: 0; + background: transparent; + color: var(--fg); + cursor: pointer; + padding: 0 0.15rem; +} + +.account__trigger svg { + width: 1.2rem; + height: 1.2rem; + fill: none; + stroke: currentColor; + stroke-width: 1.5; + stroke-linecap: round; + stroke-linejoin: round; +} + +.account__name { + max-width: 7.5rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.86rem; + color: var(--fg-muted); +} + +.account__trigger:hover, +.account__trigger:hover .account__name { + color: var(--accent); +} + +.account__menu { + position: absolute; + inset-inline-start: 0; + top: calc(100% + 0.5rem); + z-index: 30; + min-width: 10rem; + display: flex; + flex-direction: column; + border: 1px solid var(--line); + background: #000; + padding: 0.35rem 0; +} + +.account__menu a { + padding: 0.55rem 0.9rem; + color: var(--fg-muted); + font-size: 0.92rem; +} + +.account__menu a:hover { + color: var(--accent); +} + +.cart { + position: relative; +} + +.cart__trigger { + position: relative; +} + +.cart__badge { + position: absolute; + inset-inline-end: -0.15rem; + top: -0.15rem; + min-width: 1rem; + height: 1rem; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--accent); + background: #000; + color: var(--accent); + font-size: 0.62rem; + line-height: 1; + padding-inline: 0.15rem; +} + +.cart__popup { + position: absolute; + inset-inline-start: 0; + top: calc(100% + 0.5rem); + z-index: 30; + width: min(20rem, calc(100vw - 2rem)); + border: 1px solid var(--line); + background: #000; + padding: 0.9rem; +} + +.cart__title { + margin: 0 0 0.75rem; + color: var(--accent); + font-size: 0.9rem; +} + +.cart__empty { + margin: 0; + color: var(--fg-muted); + font-size: 0.92rem; +} + +.cart__list { + list-style: none; + margin: 0; + padding: 0; + max-height: 18rem; + overflow: auto; +} + +.cart__list > li { + display: grid; + grid-template-columns: 3rem 1fr auto; + gap: 0.65rem; + align-items: start; + padding: 0.65rem 0; + border-top: 1px solid var(--line); +} + +.cart__list img, +.cart__placeholder { + width: 3rem; + height: 3rem; + object-fit: contain; + border: 1px solid var(--line); + background: #0c0c0e; +} + +.cart__list p { + margin: 0; + font-size: 0.88rem; + line-height: 1.5; +} + +.cart__qty, +.buy__qty { + display: inline-flex; + align-items: center; + gap: 0.45rem; + margin-top: 0.35rem; +} + +.cart__qty button, +.buy__qty button, +.buy__variants button { + width: 1.5rem; + height: 1.5rem; + border: 1px solid var(--line); + background: transparent; + cursor: pointer; +} + +.cart__side { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.35rem; + font-size: 0.82rem; +} + +.cart__side button { + border: 0; + background: transparent; + color: var(--fg-muted); + cursor: pointer; + padding: 0; +} + +.cart__side button:hover { + color: var(--accent); +} + +.cart__foot { + margin-top: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid var(--line); + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.cart__foot p { + margin: 0; + display: flex; + justify-content: space-between; + gap: 1rem; +} + +.cart__foot span { + color: var(--fg-muted); +} + +.toast-stack { + position: fixed; + inset-inline: 0; + bottom: 1.25rem; + z-index: 80; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + pointer-events: none; + padding-inline: 1rem; +} + +.toast { + pointer-events: auto; + max-width: 22rem; + border: 1px solid var(--accent); + background: #101012; + color: var(--fg); + padding: 0.7rem 1rem; + font-size: 0.92rem; +} + +.toast--error { + border-color: #e7b4b4; + color: #e7b4b4; +} + +.media-gallery__stage { + position: relative; + border: 1px solid var(--line); + line-height: 0; + cursor: zoom-in; + overflow: hidden; +} + +.media-gallery__stage > img { + display: block; + width: 100%; + height: auto; +} + +.media-gallery__zoom { + display: none; + position: absolute; + inset: 0; + background-repeat: no-repeat; + background-size: 220%; + pointer-events: none; +} + +@media (hover: hover) and (min-width: 901px) { + .media-gallery__zoom { + display: block; + } +} + +.media-gallery__nav, +.media-lightbox__nav { + position: absolute; + top: 50%; + inset-inline-start: 0.6rem; + translate: 0 -50%; + width: 2.2rem; + height: 2.2rem; + border: 1px solid var(--line); + background: rgba(0, 0, 0, 0.55); + color: var(--fg); + cursor: pointer; +} + +.media-gallery__nav--next, +.media-lightbox__nav--next { + inset-inline-start: auto; + inset-inline-end: 0.6rem; +} + +.media-gallery__thumbs { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0.75rem 0 0; + padding: 0; + list-style: none; +} + +.media-gallery__thumbs button { + width: 3.5rem; + height: 4.5rem; + padding: 0; + border: 1px solid var(--line); + background: #0c0c0e; + cursor: pointer; +} + +.media-gallery__thumbs button.is-active { + border-color: var(--accent); +} + +.media-gallery__thumbs img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.media-lightbox { + position: fixed; + inset: 0; + z-index: 60; + display: flex; + flex-direction: column; + background: rgba(0, 0, 0, 0.94); +} + +.media-lightbox__bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + color: var(--fg-muted); +} + +.media-lightbox__bar button { + border: 1px solid var(--line); + background: transparent; + padding: 0.35rem 0.8rem; + cursor: pointer; +} + +.media-lightbox__body { + position: relative; + flex: 1; + min-height: 0; +} + +.media-lightbox__body img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.detail-tabs { + margin-top: 2.5rem; + padding-bottom: 2rem; +} + +.detail-tabs__nav { + display: flex; + flex-wrap: wrap; + gap: 0.35rem 1.25rem; + border-bottom: 1px solid var(--line); +} + +.detail-tabs__nav button { + border: 0; + border-bottom: 1px solid transparent; + background: transparent; + margin-bottom: -1px; + padding: 0.55rem 0; + color: var(--fg-muted); + cursor: pointer; +} + +.detail-tabs__nav button[aria-selected="true"], +.detail-tabs__nav button:hover { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.detail-tabs__panel { + padding-top: 1.5rem; +} + +.specs { + margin: 0; + border: 1px solid var(--line); +} + +.specs > div { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 0.9rem; + border-top: 1px solid var(--line); +} + +.specs > div:first-child { + border-top: 0; +} + +.specs dt { + color: var(--fg-muted); +} + +.specs dd { + margin: 0; +} + +.comments__login { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.85rem; + margin-bottom: 1.5rem; + padding: 1.25rem; + border: 1px dashed var(--line); +} + +.comments__login p { + margin: 0; + color: var(--fg-muted); +} + +.comments__list { + list-style: none; + margin: 1.25rem 0 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.85rem; +} + +.comments__list > li { + border: 1px solid var(--line); + padding: 0.9rem; +} + +.comments__author { + margin: 0 0 0.35rem; + color: var(--accent); + font-size: 0.9rem; +} + +.buy { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.85rem; + margin-top: 1.25rem; +} + +.buy__variants ul { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + margin: 0.45rem 0 0; + padding: 0; + list-style: none; +} + +.buy__variants button[aria-pressed="true"] { + border-color: var(--accent); + color: var(--accent); +} + +.buy__price { + margin: 0; + color: var(--accent); + font-size: 1.1rem; +} + +.buy__price span { + color: var(--fg-muted); + font-size: 0.85rem; +} + +.pager { + display: flex; + align-items: center; + justify-content: center; + gap: 1.25rem; + margin-top: 2rem; + color: var(--fg-muted); +} + +.pager a { + color: var(--accent); +} + +.article { + padding-top: clamp(2.5rem, 6vw, 4.5rem); + padding-bottom: 2rem; +} + +.article .page-intro { + padding-top: 0; +} + +.article__figure { + margin: 0 0 1.5rem; + border: 1px solid var(--line); + background: #0c0c0e; +} + +.article__figure img { + width: 100%; + max-height: 28rem; + object-fit: contain; +} + +.rich-text { + color: var(--fg-muted); + line-height: 1.9; +} + +.rich-text p { + margin: 0 0 1rem; +} + +.rich-text img { + max-width: 100%; + height: auto; +} + +@media (max-width: 720px) { + .account__name { + display: none; + } + + .cart__popup { + inset-inline: auto 0; + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9958db6..7d94834 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,21 @@ import type { Metadata } from "next"; import { Cinzel } from "next/font/google"; import localFont from "next/font/local"; +import Script from "next/script"; +import { CmsMeta, JsonLd } from "@/components/CmsMeta"; +import { ContentSequence } from "@/components/ContentSequence"; +import { Footer } from "@/components/Footer"; +import { Header } from "@/components/Header"; +import { CartProvider } from "@/components/providers/CartProvider"; +import { ToastProvider } from "@/components/providers/ToastProvider"; import { site } from "@/data/site"; +import { getSession } from "@/lib/auth"; +import { getCategories, safe } from "@/lib/catalog"; +import { megaMenuGroups } from "@/lib/categoryTree"; +import { resolveAddress, resolveInstagram, resolveLogo, resolvePhones } from "@/lib/content"; +import { siteUrl } from "@/lib/config"; +import { getBusinessInfo, getFavicon } from "@/lib/meshkee"; import "./globals.css"; @@ -15,47 +28,86 @@ const cinzel = Cinzel({ const doran = localFont({ src: [ - { - path: "../fonts/doran/Doran-Thin.woff", - weight: "100", - style: "normal", - }, - { - path: "../fonts/doran/Doran-Regular.woff", - weight: "400", - style: "normal", - }, - { - path: "../fonts/doran/Doran-Medium.woff", - weight: "500", - style: "normal", - }, - { - path: "../fonts/doran/Doran-Bold.woff", - weight: "700", - style: "normal", - }, + { path: "../fonts/doran/Doran-Thin.woff", weight: "100", style: "normal" }, + { path: "../fonts/doran/Doran-Regular.woff", weight: "400", style: "normal" }, + { path: "../fonts/doran/Doran-Medium.woff", weight: "500", style: "normal" }, + { path: "../fonts/doran/Doran-Bold.woff", weight: "700", style: "normal" }, ], variable: "--font-doran", display: "swap", }); -export const metadata: Metadata = { - title: `${site.nameFa} | ${site.tagline}`, - description: site.description, - icons: { - icon: "/Ayoobi-logo.png", - }, -}; +const iranYekan = localFont({ + src: [{ path: "../fonts/iranyekan/IranYekan-Light.woff", weight: "300", style: "normal" }], + variable: "--font-iranyekan", + display: "swap", +}); + +export async function generateMetadata(): Promise { + const favicon = await getFavicon(); + const icon = favicon.faviconUrl || "/Ayoobi-logo.png"; + + return { + metadataBase: new URL(siteUrl), + title: { + default: `${site.nameFa} | طلا و جواهر در قم`, + template: `%s | ${site.shortFa}`, + }, + description: site.description, + icons: [{ url: icon, type: "image/png" }], + openGraph: { + type: "website", + locale: "fa_IR", + siteName: site.nameFa, + title: site.nameFa, + description: site.description, + images: [{ url: "/Ayoobi-logo.png", alt: site.nameFa }], + }, + }; +} + +export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + const [info, user, categories] = await Promise.all([ + getBusinessInfo(), + getSession(), + safe(getCategories("product"), []), + ]); + const logo = resolveLogo(info); + const instagram = resolveInstagram(info); + const jsonLd = info.schema?.enabled ? info.schema.jsonLd : null; + const categoryGroups = megaMenuGroups(categories); -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { return ( - - {children} + + + + + + + + + + +
+ {children} +