From 7d26eaa838f4aaf457c647a92746352cc65a56da Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Sun, 16 Aug 2026 01:36:29 +0330 Subject: [PATCH] Build the Salar Store storefront and connect products, blogs, and home specials to the Meshkee API. Co-authored-by: Cursor --- app/aboutus/page.tsx | 57 +++ app/api/comments/route.ts | 12 + app/api/search/route.ts | 11 + app/blog/[slug]/page.tsx | 63 +++ app/blog/page.tsx | 65 +++ app/contactus/page.tsx | 94 ++++ app/globals.css | 268 ++++++++++- app/layout.tsx | 41 +- app/not-found.tsx | 13 + app/page.tsx | 83 +--- app/products/[slug]/page.tsx | 111 +++++ app/products/page.tsx | 40 ++ components/AboutUs.tsx | 29 ++ components/Banners.tsx | 48 ++ components/BlogCarousel.tsx | 38 ++ components/Brands.tsx | 21 + components/Carousel.tsx | 53 +++ components/Categories.tsx | 41 ++ components/ContactForm.tsx | 73 +++ components/DrawFrame.tsx | 3 + components/Footer.tsx | 120 +++++ components/Header.tsx | 159 +++++++ components/HeroSlider.tsx | 106 +++++ components/Icons.tsx | 108 +++++ components/Media.tsx | 27 ++ components/PageHero.tsx | 46 ++ components/Pagination.tsx | 38 ++ components/ProductCard.tsx | 40 ++ components/ProductGrid.tsx | 18 + components/ProductShelf.tsx | 38 ++ components/ProductTabs.tsx | 172 +++++++ components/ProductView.tsx | 156 +++++++ components/RichContent.tsx | 5 + components/SearchBox.tsx | 137 ++++++ components/SpecialSale.tsx | 17 + components/TopHeader.tsx | 35 ++ components/TopSelling.tsx | 17 + lib/api.ts | 211 +++++++++ lib/cart.ts | 39 ++ lib/data.ts | 438 ++++++++++++++++++ lib/format.ts | 14 + lib/mappers.ts | 150 ++++++ lib/samples.ts | 30 ++ lib/site.ts | 29 ++ next.config.ts | 12 +- package.json | 2 +- prompt.txt | 37 ++ public/images/brands/armour.png | Bin 0 -> 4862 bytes public/images/brands/deuter.png | Bin 0 -> 12202 bytes public/images/brands/gerber.png | Bin 0 -> 6956 bytes public/images/brands/jack-wolfskin.png | Bin 0 -> 19729 bytes public/images/brands/mammut.png | Bin 0 -> 12387 bytes public/images/brands/north-face.png | Bin 0 -> 8655 bytes public/images/brands/pekvnew.webp | Bin 0 -> 12136 bytes public/images/brands/petzl.png | Bin 0 -> 17437 bytes public/images/brands/salewa.png | Bin 0 -> 729 bytes public/images/brands/salomon.png | Bin 0 -> 6813 bytes public/images/brands/scarpa.png | Bin 0 -> 13619 bytes public/images/brands/wenger.png | Bin 0 -> 7367 bytes public/images/categories/backpack.png | Bin 0 -> 32169 bytes public/images/categories/cookware.png | Bin 0 -> 29437 bytes public/images/categories/hat.png | Bin 0 -> 27858 bytes public/images/categories/headlamp.png | Bin 0 -> 24623 bytes public/images/categories/knife.png | Bin 0 -> 29095 bytes public/images/categories/pants.png | Bin 0 -> 9208 bytes public/images/categories/pole.png | Bin 0 -> 16060 bytes public/images/categories/shoe.png | Bin 0 -> 35150 bytes public/images/categories/sleeping-bag.png | Bin 0 -> 19079 bytes public/images/categories/stove.png | Bin 0 -> 23565 bytes public/images/categories/tent.png | Bin 0 -> 23471 bytes public/images/categories/tshirt.png | Bin 0 -> 31724 bytes public/images/icons/guarantee.webp | Bin 0 -> 1078 bytes public/images/icons/nationwide.webp | Bin 0 -> 1558 bytes public/images/icons/shipping.webp | Bin 0 -> 1456 bytes public/images/icons/support.webp | Bin 0 -> 1564 bytes public/images/logo/logo-footer.png | Bin 0 -> 13156 bytes public/images/logo/logo.png | Bin 0 -> 13822 bytes public/images/logo/meshkee-white.png | Bin 0 -> 33035 bytes public/images/pages/about-bg.webp | Bin 0 -> 168874 bytes public/images/pages/about-side.png | Bin 0 -> 664824 bytes public/images/placeholders/about.png | Bin 0 -> 1943 bytes public/images/placeholders/banner-3-1.png | Bin 0 -> 748 bytes public/images/placeholders/banner-3-2.png | Bin 0 -> 748 bytes public/images/placeholders/banner-3-3.png | Bin 0 -> 748 bytes public/images/placeholders/banner-4-1.png | Bin 0 -> 2252 bytes public/images/placeholders/banner-4-2.png | Bin 0 -> 2252 bytes public/images/placeholders/banner-4-3.png | Bin 0 -> 1169 bytes public/images/placeholders/banner-4-4.png | Bin 0 -> 1169 bytes public/images/placeholders/blog.png | Bin 0 -> 1943 bytes public/images/placeholders/product.png | Bin 0 -> 1891 bytes public/images/placeholders/slider-1.png | Bin 0 -> 2767 bytes public/images/placeholders/slider-2.png | Bin 0 -> 2767 bytes public/images/placeholders/slider-3.png | Bin 0 -> 2767 bytes public/images/placeholders/slider-side-1.png | Bin 0 -> 906 bytes public/images/placeholders/slider-side-2.png | Bin 0 -> 906 bytes public/images/placeholders/special-sale.png | Bin 0 -> 947 bytes public/images/promo/special-sale-alt.png | Bin 0 -> 83361 bytes public/images/promo/special-sale.png | Bin 0 -> 55353 bytes public/images/promo/special-sale.webp | Bin 0 -> 10204 bytes public/images/promo/top-selling.png | Bin 0 -> 53218 bytes public/images/promo/top-selling.webp | Bin 0 -> 9868 bytes .../braden-collum-9HI8UJMSdZA-unsplash.jpg | Bin 0 -> 2439470 bytes .../fabio-comparelli-uq2E2V4LhCY-unsplash.jpg | Bin 0 -> 990980 bytes .../glen-jackson-mzZVGFfMOkA-unsplash.jpg | Bin 0 -> 446118 bytes .../samples/henry-xu-0XHo26Jb8dk-unsplash.jpg | Bin 0 -> 457505 bytes .../jesse-gardner-wTVr4HR4SBI-unsplash.jpg | Bin 0 -> 831434 bytes .../jisca-lucia-HVTVOWDKTCk-unsplash.jpg | Bin 0 -> 1542497 bytes .../kevin-schmid-AvEY5UPoJZM-unsplash.jpg | Bin 0 -> 483174 bytes .../paul-green-gOHfFgwyDNM-unsplash.jpg | Bin 0 -> 1893671 bytes .../phillips-jacobe-3Oq0jNYLp28-unsplash.jpg | Bin 0 -> 996588 bytes .../sean-benesh-VnmbcgAfL3Q-unsplash.jpg | Bin 0 -> 818617 bytes .../sylvain-mauroux-OIuzDRYA3cw-unsplash.jpg | Bin 0 -> 782278 bytes ...ictor-larracuente-JQfYGhUcDSg-unsplash.jpg | Bin 0 -> 844397 bytes 113 files changed, 3269 insertions(+), 96 deletions(-) create mode 100644 app/aboutus/page.tsx create mode 100644 app/api/comments/route.ts create mode 100644 app/api/search/route.ts create mode 100644 app/blog/[slug]/page.tsx create mode 100644 app/blog/page.tsx create mode 100644 app/contactus/page.tsx create mode 100644 app/not-found.tsx create mode 100644 app/products/[slug]/page.tsx create mode 100644 app/products/page.tsx create mode 100644 components/AboutUs.tsx create mode 100644 components/Banners.tsx create mode 100644 components/BlogCarousel.tsx create mode 100644 components/Brands.tsx create mode 100644 components/Carousel.tsx create mode 100644 components/Categories.tsx create mode 100644 components/ContactForm.tsx create mode 100644 components/DrawFrame.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Header.tsx create mode 100644 components/HeroSlider.tsx create mode 100644 components/Icons.tsx create mode 100644 components/Media.tsx create mode 100644 components/PageHero.tsx create mode 100644 components/Pagination.tsx create mode 100644 components/ProductCard.tsx create mode 100644 components/ProductGrid.tsx create mode 100644 components/ProductShelf.tsx create mode 100644 components/ProductTabs.tsx create mode 100644 components/ProductView.tsx create mode 100644 components/RichContent.tsx create mode 100644 components/SearchBox.tsx create mode 100644 components/SpecialSale.tsx create mode 100644 components/TopHeader.tsx create mode 100644 components/TopSelling.tsx create mode 100644 lib/api.ts create mode 100644 lib/cart.ts create mode 100644 lib/data.ts create mode 100644 lib/format.ts create mode 100644 lib/mappers.ts create mode 100644 lib/samples.ts create mode 100644 lib/site.ts create mode 100644 prompt.txt create mode 100644 public/images/brands/armour.png create mode 100644 public/images/brands/deuter.png create mode 100644 public/images/brands/gerber.png create mode 100644 public/images/brands/jack-wolfskin.png create mode 100644 public/images/brands/mammut.png create mode 100644 public/images/brands/north-face.png create mode 100644 public/images/brands/pekvnew.webp create mode 100644 public/images/brands/petzl.png create mode 100644 public/images/brands/salewa.png create mode 100644 public/images/brands/salomon.png create mode 100644 public/images/brands/scarpa.png create mode 100644 public/images/brands/wenger.png create mode 100644 public/images/categories/backpack.png create mode 100644 public/images/categories/cookware.png create mode 100644 public/images/categories/hat.png create mode 100644 public/images/categories/headlamp.png create mode 100644 public/images/categories/knife.png create mode 100644 public/images/categories/pants.png create mode 100644 public/images/categories/pole.png create mode 100644 public/images/categories/shoe.png create mode 100644 public/images/categories/sleeping-bag.png create mode 100644 public/images/categories/stove.png create mode 100644 public/images/categories/tent.png create mode 100644 public/images/categories/tshirt.png create mode 100644 public/images/icons/guarantee.webp create mode 100644 public/images/icons/nationwide.webp create mode 100644 public/images/icons/shipping.webp create mode 100644 public/images/icons/support.webp create mode 100644 public/images/logo/logo-footer.png create mode 100644 public/images/logo/logo.png create mode 100644 public/images/logo/meshkee-white.png create mode 100644 public/images/pages/about-bg.webp create mode 100644 public/images/pages/about-side.png create mode 100644 public/images/placeholders/about.png create mode 100644 public/images/placeholders/banner-3-1.png create mode 100644 public/images/placeholders/banner-3-2.png create mode 100644 public/images/placeholders/banner-3-3.png create mode 100644 public/images/placeholders/banner-4-1.png create mode 100644 public/images/placeholders/banner-4-2.png create mode 100644 public/images/placeholders/banner-4-3.png create mode 100644 public/images/placeholders/banner-4-4.png create mode 100644 public/images/placeholders/blog.png create mode 100644 public/images/placeholders/product.png create mode 100644 public/images/placeholders/slider-1.png create mode 100644 public/images/placeholders/slider-2.png create mode 100644 public/images/placeholders/slider-3.png create mode 100644 public/images/placeholders/slider-side-1.png create mode 100644 public/images/placeholders/slider-side-2.png create mode 100644 public/images/placeholders/special-sale.png create mode 100644 public/images/promo/special-sale-alt.png create mode 100644 public/images/promo/special-sale.png create mode 100644 public/images/promo/special-sale.webp create mode 100644 public/images/promo/top-selling.png create mode 100644 public/images/promo/top-selling.webp create mode 100644 src/assets/images/samples/braden-collum-9HI8UJMSdZA-unsplash.jpg create mode 100644 src/assets/images/samples/fabio-comparelli-uq2E2V4LhCY-unsplash.jpg create mode 100644 src/assets/images/samples/glen-jackson-mzZVGFfMOkA-unsplash.jpg create mode 100644 src/assets/images/samples/henry-xu-0XHo26Jb8dk-unsplash.jpg create mode 100644 src/assets/images/samples/jesse-gardner-wTVr4HR4SBI-unsplash.jpg create mode 100644 src/assets/images/samples/jisca-lucia-HVTVOWDKTCk-unsplash.jpg create mode 100644 src/assets/images/samples/kevin-schmid-AvEY5UPoJZM-unsplash.jpg create mode 100644 src/assets/images/samples/paul-green-gOHfFgwyDNM-unsplash.jpg create mode 100644 src/assets/images/samples/phillips-jacobe-3Oq0jNYLp28-unsplash.jpg create mode 100644 src/assets/images/samples/sean-benesh-VnmbcgAfL3Q-unsplash.jpg create mode 100644 src/assets/images/samples/sylvain-mauroux-OIuzDRYA3cw-unsplash.jpg create mode 100644 src/assets/images/samples/victor-larracuente-JQfYGhUcDSg-unsplash.jpg diff --git a/app/aboutus/page.tsx b/app/aboutus/page.tsx new file mode 100644 index 0000000..91fdf9f --- /dev/null +++ b/app/aboutus/page.tsx @@ -0,0 +1,57 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import { PageHero } from "@/components/PageHero"; +import { SITE } from "@/lib/site"; + +export const metadata: Metadata = { + title: `درباره ما | ${SITE.nameFa}`, + description: SITE.about, +}; + +export default function AboutPage() { + return ( + <> + +
+
+ +
+
+

{SITE.nameFa}

+

{SITE.about}

+
+
+ +
+
+

SALAR STORE

+

سالار استور

+

{SITE.about}

+
+
+ {SITE.nameFa} +
+
+
+ + ); +} diff --git a/app/api/comments/route.ts b/app/api/comments/route.ts new file mode 100644 index 0000000..a7ad676 --- /dev/null +++ b/app/api/comments/route.ts @@ -0,0 +1,12 @@ +import { API } from "@/lib/site"; + +export async function POST(request: Request) { + const body = await request.json(); + const response = await fetch(`${API.base}/tenants/${API.domain}/comments`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const data = await response.json(); + return Response.json(data, { status: response.status }); +} diff --git a/app/api/search/route.ts b/app/api/search/route.ts new file mode 100644 index 0000000..96a28b0 --- /dev/null +++ b/app/api/search/route.ts @@ -0,0 +1,11 @@ +import { listMappedProducts } from "@/lib/mappers"; + +export async function GET(request: Request) { + const query = new URL(request.url).searchParams.get("q")?.trim() ?? ""; + if (query.length < 2) { + return Response.json({ items: [] }); + } + + const data = await listMappedProducts({ name: query, page: 1, pageSize: 8 }); + return Response.json({ items: data.items, total: data.total }); +} diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..62b0b73 --- /dev/null +++ b/app/blog/[slug]/page.tsx @@ -0,0 +1,63 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { Media } from "@/components/Media"; +import { PageHero } from "@/components/PageHero"; +import { RichContent } from "@/components/RichContent"; +import { getBlog } from "@/lib/api"; +import { formatDate } from "@/lib/format"; +import { PLACEHOLDER } from "@/lib/data"; +import { SITE } from "@/lib/site"; + +type Props = { + params: Promise<{ slug: string }>; +}; + +export async function generateMetadata({ params }: Props): Promise { + const { slug } = await params; + try { + const blog = await getBlog(slug); + if (!blog) return { title: SITE.nameFa }; + return { + title: `${blog.title} | ${SITE.nameFa}`, + description: blog.abstract || SITE.aboutShort, + }; + } catch { + return { title: SITE.nameFa }; + } +} + +export default async function BlogDetailPage({ params }: Props) { + const { slug } = await params; + const blog = await getBlog(slug); + if (!blog) notFound(); + + const author = [blog.author?.firstName, blog.author?.lastName].filter(Boolean).join(" "); + + return ( + <> + +
+ +
+ {blog.publishedAt || blog.createdAt ? : null} + {blog.categoryName ? {blog.categoryName} : null} + {author ? {author} : null} +
+ {blog.abstract ?

{blog.abstract}

: null} +
+ +
+
+ + ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000..319b53f --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,65 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { Media } from "@/components/Media"; +import { PageHero } from "@/components/PageHero"; +import { Pagination } from "@/components/Pagination"; +import { getBlogs } from "@/lib/api"; +import { mapBlog } from "@/lib/mappers"; +import { SITE } from "@/lib/site"; + +export const metadata: Metadata = { + title: `وبلاگ | ${SITE.nameFa}`, + description: "آخرین مقالات سالار استور", +}; + +type Props = { + searchParams: Promise<{ page?: string }>; +}; + +export default async function BlogPage({ searchParams }: Props) { + const { page: pageParam } = await searchParams; + const page = Math.max(1, Number(pageParam) || 1); + let data: Awaited> = { items: [], total: 0, page, pageSize: 12 }; + try { + data = await getBlogs({ page, pageSize: 12 }); + } catch { + data = { items: [], total: 0, page, pageSize: 12 }; + } + const posts = data.items.map(mapBlog); + + return ( + <> + +
+ {posts.length ? ( +
+ {posts.map((post) => ( + + +
+ +

{post.title}

+ {post.abstract ? ( +

{post.abstract}

+ ) : null} +
+ + ))} +
+ ) : ( +

مقاله‌ای یافت نشد.

+ )} + (nextPage > 1 ? `/blog?page=${nextPage}` : "/blog")} + /> +
+ + ); +} diff --git a/app/contactus/page.tsx b/app/contactus/page.tsx new file mode 100644 index 0000000..779de81 --- /dev/null +++ b/app/contactus/page.tsx @@ -0,0 +1,94 @@ +import type { Metadata } from "next"; +import { ContactForm } from "@/components/ContactForm"; +import { PageHero } from "@/components/PageHero"; +import { IconInstagram, IconPhone, IconPin, IconTelegram, IconWhatsapp } from "@/components/Icons"; +import { SITE } from "@/lib/site"; + +export const metadata: Metadata = { + title: `تماس با ما | ${SITE.nameFa}`, + description: SITE.address, +}; + +const contacts = [ + { + href: `tel:${SITE.phone}`, + label: SITE.phoneDisplay, + dir: "ltr" as const, + numeric: true, + icon: IconPhone, + }, + { + href: SITE.whatsapp, + label: SITE.phoneDisplay, + dir: "ltr" as const, + numeric: true, + icon: IconWhatsapp, + }, + { + href: SITE.instagram, + label: SITE.instagramHandle, + dir: "ltr" as const, + numeric: false, + icon: IconInstagram, + }, + { + href: SITE.telegram, + label: SITE.telegramHandle, + dir: "ltr" as const, + numeric: false, + icon: IconTelegram, + }, + { + href: undefined, + label: SITE.address, + dir: undefined, + numeric: false, + icon: IconPin, + }, +]; + +export default function ContactPage() { + return ( + <> + +
+
+
+ {contacts.map((item) => { + const Icon = item.icon; + const content = ( + <> + + + + + {item.label} + + + ); + + if (!item.href) { + return ( +
+ {content} +
+ ); + } + + return ( + + {content} + + ); + })} +
+ +
+
+ + ); +} diff --git a/app/globals.css b/app/globals.css index a2dc41e..910a65a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,260 @@ @import "tailwindcss"; -:root { - --background: #ffffff; - --foreground: #171717; -} - @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --color-background: #ffffff; + --color-foreground: #292d32; + --color-brand: #fc2424; + --color-brand-soft: #ffecec; + --color-ink: #292d32; + --color-muted: #f5f6f9; + --font-sans: var(--font-vazir), Tahoma, sans-serif; + --font-oswald: var(--font-oswald), Impact, sans-serif; + --font-carter: var(--font-carter-one), Impact, sans-serif; } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } +html { + scroll-behavior: smooth; } body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + background: #ffffff; + color: var(--color-ink); + font-family: var(--font-sans); +} + +.font-oswald { + font-family: var(--font-oswald); +} + +.font-carter { + font-family: var(--font-carter); +} + +.container-site { + width: min(1200px, calc(100% - 2rem)); + margin-inline: auto; +} + +.section-title { + font-size: 1.25rem; + font-weight: 800; + color: var(--color-ink); +} + +.page-hero { + background-color: #fbfbfc; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Ccircle cx='2' cy='2' r='1' fill='%23292d32' fill-opacity='0.06'/%3E%3C/svg%3E"); + background-size: 28px 28px; +} + +::selection { + background: #fc2424; + color: #fff; +} + +.footer-bottom-bar { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: space-between; +} + +.footer-bottom-site { + color: #9c9c9c; + font-size: 12px; +} + +.meshkee-credit { + align-items: center; + color: #9c9c9c; + display: inline-flex; + gap: 10px; + padding: 6px 10px; + position: relative; + text-decoration: none; + transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover { + color: #e0e0e0; + text-decoration: none; +} + +.meshkee-credit__frame { + inset: 0; + pointer-events: none; + position: absolute; +} + +.meshkee-credit__line { + background: #777; + position: absolute; + transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit__line--l { + height: 100%; + left: 0; + top: 0; + transform: scaleY(0); + transform-origin: top; + transition-delay: 0s; + width: 1px; +} + +.meshkee-credit__line--b { + bottom: 0; + height: 1px; + left: 0; + transform: scaleX(0); + transform-origin: left; + transition-delay: 0.14s; + width: 100%; +} + +.meshkee-credit__line--r { + height: 100%; + right: 0; + top: 0; + transform: scaleY(0); + transform-origin: bottom; + transition-delay: 0.28s; + width: 1px; +} + +.meshkee-credit__line--t { + height: 1px; + left: 0; + top: 0; + transform: scaleX(0); + transform-origin: right; + transition-delay: 0.42s; + width: 100%; +} + +.meshkee-credit:hover .meshkee-credit__line--l, +.meshkee-credit:hover .meshkee-credit__line--r { + transform: scaleY(1); +} + +.meshkee-credit:hover .meshkee-credit__line--b, +.meshkee-credit:hover .meshkee-credit__line--t { + transform: scaleX(1); +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--l { + transition-delay: 0.42s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--b { + transition-delay: 0.28s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--r { + transition-delay: 0.14s; +} + +.meshkee-credit:not(:hover) .meshkee-credit__line--t { + transition-delay: 0s; +} + +.meshkee-credit__logo { + aspect-ratio: 1 / 1; + display: block; + flex-shrink: 0; + height: 42px; + mix-blend-mode: screen; + object-fit: contain; + opacity: 0.7; + position: relative; + width: auto; + transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover .meshkee-credit__logo { + opacity: 1; +} + +.meshkee-credit__text { + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1.25; + min-height: 42px; + position: relative; +} + +.meshkee-credit__label { + font-size: 11px; +} + +.meshkee-credit__name { + color: #cfcfcf; + font-size: 13px; + font-weight: 600; + transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} + +.meshkee-credit:hover .meshkee-credit__name { + color: #fff; +} + +@media (max-width: 575px) { + .footer-bottom-bar { + flex-direction: column; + justify-content: center; + text-align: center; + } +} + +.draw-frame { + position: absolute; + inset: 0; + z-index: 5; + pointer-events: none; + border-radius: inherit; + border: 1.5px solid currentColor; + opacity: 0; + transition: opacity 0.25s ease; +} + +.draw-frame-host:hover .draw-frame { + opacity: 1; +} + +.rich-content { + color: #444; + line-height: 2; +} + +.rich-content h2, +.rich-content h3, +.rich-content h4 { + color: var(--color-ink); + font-weight: 800; + margin: 1.5rem 0 0.75rem; +} + +.rich-content p, +.rich-content li { + margin-bottom: 0.85rem; +} + +.rich-content ul, +.rich-content ol { + padding-inline-start: 1.25rem; + margin-bottom: 1rem; +} + +.rich-content a { + color: var(--color-brand); +} + +.rich-content img { + border-radius: 12px; + display: block; + height: auto; + margin: 1.25rem auto; + max-width: 100%; } diff --git a/app/layout.tsx b/app/layout.tsx index 9852c15..b3d04bc 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,29 +1,44 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Carter_One, Oswald, Vazirmatn } from "next/font/google"; +import { Footer } from "@/components/Footer"; +import { Header } from "@/components/Header"; +import { TopHeader } from "@/components/TopHeader"; +import { SITE } from "@/lib/site"; import "./globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], +const vazir = Vazirmatn({ + subsets: ["arabic", "latin"], + variable: "--font-vazir", + display: "swap", }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const oswald = Oswald({ subsets: ["latin"], + variable: "--font-oswald", + display: "swap", +}); + +const carter = Carter_One({ + weight: "400", + subsets: ["latin"], + variable: "--font-carter-one", + display: "swap", }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: `${SITE.nameFa} | ${SITE.tagline}`, + description: SITE.aboutShort, }; export default function RootLayout({ children }: LayoutProps<"/">) { return ( - - {children} + + + +
+
{children}
+