Update carousels, card hover, and IranBay branding.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-04 13:21:51 +03:30
co-authored by Cursor
commit 42aff5aee9
162 changed files with 16828 additions and 0 deletions
+369
View File
@@ -0,0 +1,369 @@
.page {
background: var(--color-white);
}
.container {
max-width: var(--container-width);
margin: 0 auto;
padding: 0 var(--page-pad);
}
.wide {
max-width: 1512px;
margin: 0 auto;
padding: 0 var(--page-pad);
}
.intro {
display: grid;
grid-template-columns: minmax(240px, 0.85fr) minmax(0, 1.15fr);
gap: 48px;
align-items: center;
padding: 56px 0 72px;
}
.visual {
position: relative;
width: min(100%, 380px);
aspect-ratio: 466 / 634;
margin-inline: auto 0;
}
.visualBack {
position: absolute;
inset: 0 18% 12% 0;
background: var(--color-card);
border-radius: 32px;
}
.visualCard {
position: absolute;
left: 14%;
top: 12%;
width: 86%;
height: 88%;
border-radius: 32px;
object-fit: cover;
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.08);
}
.kicker {
font-size: clamp(22px, 2.2vw, 35px);
font-weight: 300;
color: #434343;
margin-bottom: 8px;
}
.title {
font-size: clamp(34px, 3.4vw, 55px);
font-weight: 700;
line-height: 1.1;
margin-bottom: 28px;
}
.title span {
display: block;
}
.copy {
font-size: 16px;
font-weight: 400;
line-height: 1.45;
color: #bababa;
margin-bottom: 18px;
max-width: 650px;
text-align: justify;
}
.cta {
margin-top: 18px;
height: 34px;
padding: 0 16px;
min-width: 207px;
font-size: 12px;
letter-spacing: 2.4px;
}
.clients {
padding: 24px 0 56px;
}
.clientBar {
margin-top: 36px;
background: #f8f8f8;
border-radius: 5px;
min-height: 160px;
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
align-items: center;
gap: 24px;
padding: 24px 40px;
}
.clientLogo {
width: 100%;
max-height: 96px;
object-fit: contain;
margin: 0 auto;
}
.story {
display: grid;
grid-template-columns: minmax(240px, 0.85fr) minmax(0, 1.15fr);
gap: 40px;
padding: 40px 0 72px;
align-items: start;
}
.story .cta {
margin-top: 24px;
}
.comments {
padding: 24px 0 72px;
text-align: center;
}
.commentHead {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
margin-bottom: 8px;
}
.commentHead .sectionHead {
margin: 0;
}
.arrows {
display: flex;
gap: 6px;
}
.arrowBtn {
width: 38px;
height: 36px;
background: var(--color-card);
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
}
.arrowBtn img {
width: 8px;
height: 14px;
object-fit: contain;
}
.arrowPrev {
transform: rotate(180deg);
}
.commentCount {
font-size: 13px;
letter-spacing: 1px;
color: var(--color-soft);
min-width: 48px;
text-align: right;
}
.commentTrack {
animation: commentIn 0.35s ease;
}
@keyframes commentIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.quote {
max-width: 900px;
margin: 28px auto 18px;
font-size: 18px;
line-height: 1.45;
color: #bababa;
}
.meta {
font-size: 18px;
color: #5f5f5f;
}
.dots {
margin-top: 28px;
display: flex;
justify-content: center;
gap: 8px;
}
.dot,
.dotActive {
width: 8px;
height: 8px;
border-radius: 50%;
background: #d8d8d8;
padding: 0;
}
.dotActive {
background: var(--color-black);
}
.services {
padding: 24px 0 96px;
}
.serviceGrid {
margin-top: 48px;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 28px;
align-items: end;
}
.serviceCard {
position: relative;
min-height: 190px;
display: grid;
grid-template-columns: auto 1fr;
align-items: end;
gap: 8px;
padding: 24px 16px 18px 0;
}
.serviceCard::before {
content: "";
position: absolute;
left: 52px;
right: 0;
bottom: 18px;
height: 135px;
background: #f8f8f8;
border-radius: 25px;
z-index: 0;
}
.serviceCard img {
position: relative;
z-index: 1;
display: block;
width: auto;
height: 160px;
max-width: 150px;
object-fit: contain;
margin-left: 0;
filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.12));
}
.serviceCopy {
position: relative;
z-index: 1;
align-self: center;
padding: 8px 12px 8px 4px;
text-align: left;
}
.serviceCopy h3 {
font-size: clamp(20px, 1.8vw, 30px);
font-weight: 400;
letter-spacing: 1px;
text-transform: uppercase;
line-height: 1.1;
}
.serviceCopy p {
margin-top: 6px;
font-size: 11px;
font-weight: 300;
letter-spacing: 1.2px;
text-transform: uppercase;
color: var(--color-black);
}
.support .serviceCopy h3 {
color: #575f50;
}
.register .serviceCopy h3 {
color: #b68968;
}
.responsive .serviceCopy h3 {
color: #474039;
}
.sectionHead {
text-align: center;
}
.sectionTitle {
font-size: clamp(24px, 2.2vw, 35px);
font-weight: 400;
letter-spacing: 14px;
text-transform: uppercase;
}
.sectionSub {
margin-top: 8px;
font-size: 12px;
font-weight: 300;
letter-spacing: 4.02px;
text-transform: uppercase;
color: var(--color-soft);
}
@media (max-width: 980px) {
.intro,
.story {
grid-template-columns: 1fr;
}
.visual {
margin: 0 auto;
}
.commentHead {
grid-template-columns: 1fr;
justify-items: center;
gap: 14px;
}
.commentCount {
text-align: center;
}
.serviceGrid {
grid-template-columns: 1fr;
max-width: 420px;
margin-left: auto;
margin-right: auto;
}
.clientBar {
grid-template-columns: repeat(3, 1fr);
min-height: auto;
}
}
@media (max-width: 720px) {
.clientBar {
grid-template-columns: repeat(2, 1fr);
padding: 20px;
}
.sectionTitle {
letter-spacing: 6px;
}
.serviceCopy {
padding-left: 4px;
}
}
+117
View File
@@ -0,0 +1,117 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import CommentCarousel from "@/components/CommentCarousel";
import { aboutCopy, clients, services } from "@/data/pages";
import styles from "./about.module.css";
export const metadata = {
title: "About Us | IranBay",
};
export default function AboutPage() {
return (
<div className={styles.page}>
<Header />
<main>
<section className={styles.container}>
<div className={styles.intro}>
<div className={styles.visual}>
<div className={styles.visualBack} />
<img
className={styles.visualCard}
src="/images/about/hero-card.png"
alt="IranBay"
/>
</div>
<div>
<p className={styles.kicker}>{aboutCopy.kicker}</p>
<h1 className={styles.title}>
{aboutCopy.title.map((line) => (
<span key={line}>{line}</span>
))}
</h1>
{aboutCopy.paragraphs.map((p) => (
<p className={styles.copy} key={p.slice(0, 32)}>
{p}
</p>
))}
<a className={`outline-btn ${styles.cta}`} href="/contact">
Contact us
</a>
</div>
</div>
</section>
<section className={styles.wide}>
<div className={styles.clients}>
<div className={styles.sectionHead}>
<h2 className={styles.sectionTitle}>All clinets</h2>
<p className={styles.sectionSub}>
some of our most crowded categories
</p>
</div>
<div className={styles.clientBar}>
{clients.map((client) => (
<img
key={client.name}
src={client.image}
alt={client.name}
className={styles.clientLogo}
/>
))}
</div>
</div>
</section>
<section className={styles.container}>
<div className={styles.story}>
<div>
<p className={styles.kicker}>{aboutCopy.kicker}</p>
<h2 className={styles.title}>
{aboutCopy.title.map((line) => (
<span key={`story-${line}`}>{line}</span>
))}
</h2>
</div>
<div>
<p className={styles.copy}>{aboutCopy.paragraphs[0]}</p>
<a className={`outline-btn ${styles.cta}`} href="/contact">
Contact us
</a>
</div>
</div>
</section>
<section className={styles.container}>
<CommentCarousel />
</section>
<section className={styles.wide}>
<div className={styles.services}>
<div className={styles.sectionHead}>
<h2 className={styles.sectionTitle}>Our services</h2>
<p className={styles.sectionSub}>
some of our most crowded categories
</p>
</div>
<div className={styles.serviceGrid}>
{services.map((item) => (
<article
className={`${styles.serviceCard} ${styles[item.tone]}`}
key={item.title}
>
<img src={item.image} alt="" />
<div className={styles.serviceCopy}>
<h3>{item.title}</h3>
<p>{item.subtitle}</p>
</div>
</article>
))}
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}
+107
View File
@@ -0,0 +1,107 @@
import { NextRequest, NextResponse } from "next/server";
const MESHKEE_API_URL =
process.env.MESHKEE_API_URL?.replace(/\/$/, "") ||
process.env.NEXT_PUBLIC_MESHKEE_API_URL?.replace(/\/$/, "") ||
"https://api.meshkee.com/api/v1";
type CachedToken = { token: string; expiresAt: number };
let cachedToken: CachedToken | null = null;
function tokenExpiry(token: string) {
try {
const payload = JSON.parse(
Buffer.from(token.split(".")[1] ?? "", "base64url").toString(),
) as { exp?: number };
if (typeof payload.exp === "number") return payload.exp * 1000;
} catch {
/* ignore malformed tokens */
}
return 0;
}
function usableStaticToken() {
const token = process.env.MESHKEE_API_TOKEN?.trim();
if (!token) return null;
const expiresAt = tokenExpiry(token);
if (expiresAt && expiresAt < Date.now() + 15_000) return null;
return token;
}
async function serviceToken(): Promise<string | null> {
const now = Date.now();
if (cachedToken && cachedToken.expiresAt > now + 15_000) {
return cachedToken.token;
}
const staticToken = usableStaticToken();
if (staticToken) return staticToken;
const cellNumber = process.env.MESHKEE_SUPERADMIN_CELL?.trim();
const password = process.env.MESHKEE_SUPERADMIN_PASSWORD?.trim();
if (!cellNumber || !password) return null;
const response = await fetch(`${MESHKEE_API_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cellNumber, password }),
cache: "no-store",
});
if (!response.ok) return null;
const payload = (await response.json().catch(() => null)) as {
accessToken?: string;
} | null;
const token = payload?.accessToken?.trim();
if (!token) return null;
cachedToken = {
token,
expiresAt: tokenExpiry(token) || now + 10 * 60 * 1000,
};
return token;
}
async function authHeader(request: NextRequest): Promise<string | null> {
const incoming = request.headers.get("authorization")?.trim();
if (incoming) return incoming;
const token = await serviceToken();
return token ? `Bearer ${token}` : null;
}
export async function GET(request: NextRequest) {
const auth = await authHeader(request);
if (!auth) {
return NextResponse.json(
{
message:
"Meshkee categories need auth. Set MESHKEE_API_TOKEN or sign in on the dashboard first.",
},
{ status: 503 },
);
}
const q = request.nextUrl.searchParams.get("q")?.trim() ?? "";
const params = new URLSearchParams();
if (q) params.set("q", q);
const response = await fetch(
`${MESHKEE_API_URL}/business-categories?${params}`,
{
headers: { Authorization: auth },
cache: "no-store",
},
);
const payload = await response.json().catch(() => null);
if (!response.ok) {
return NextResponse.json(
payload ?? { message: `Meshkee error (${response.status})` },
{ status: response.status },
);
}
return NextResponse.json(payload);
}
+52
View File
@@ -0,0 +1,52 @@
import type { ReactNode } from "react";
/**
* Install Meshkee session from the URL hash before the register page reads tokens.
* Used after customer-dashboard login with ?next=https://…/register
*/
const HANDOFF_BOOT = `
(function () {
try {
var raw = location.hash ? location.hash.replace(/^#/, "") : "";
if (!raw) return;
var p = new URLSearchParams(raw);
var access = p.get("access");
var refresh = p.get("refresh");
var next = p.get("next") || "/register";
if (!access || !refresh) return;
localStorage.setItem("marketplace_access_token", access);
localStorage.setItem("marketplace_refresh_token", refresh);
var root = "localhost";
try {
document.cookie =
"marketplace_access_token=" +
encodeURIComponent(access) +
"; Path=/; Max-Age=1209600; SameSite=Lax; Domain=" +
root;
document.cookie =
"marketplace_refresh_token=" +
encodeURIComponent(refresh) +
"; Path=/; Max-Age=1209600; SameSite=Lax; Domain=" +
root;
} catch (e) {}
var target = next.charAt(0) === "/" ? next : "/" + next;
location.replace(target);
} catch (e) {}
})();
`;
export default function AuthHandoffLayout({
children,
}: {
children: ReactNode;
}) {
return (
<>
<script dangerouslySetInnerHTML={{ __html: HANDOFF_BOOT }} />
{children}
</>
);
}
+23
View File
@@ -0,0 +1,23 @@
"use client";
export default function AuthHandoffPage() {
return (
<div
style={{
minHeight: "100vh",
display: "grid",
placeItems: "center",
background: "#f6f6f6",
fontSize: 12,
letterSpacing: "0.2em",
textTransform: "uppercase",
color: "#8e8e8e",
padding: 24,
textAlign: "center",
lineHeight: 1.6,
}}
>
Signing you in…
</div>
);
}
@@ -0,0 +1,196 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad) 80px;
}
.layout {
margin-top: 36px;
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.55fr);
gap: 42px;
align-items: start;
}
.article {
min-width: 0;
}
.meta {
display: flex;
flex-wrap: wrap;
gap: 18px 28px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 300;
color: var(--color-soft);
}
.meta span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.meta img {
width: 14px;
height: 14px;
opacity: 0.7;
object-fit: contain;
}
.title {
font-size: clamp(28px, 3vw, 40px);
font-weight: 300;
line-height: 1.2;
margin-bottom: 12px;
}
.intro {
font-size: 17px;
font-weight: 300;
line-height: 18px;
color: #9d9d9d;
text-align: justify;
margin-bottom: 28px;
}
.hero {
position: relative;
width: 100%;
aspect-ratio: 1190 / 525;
border: 1px solid #eee;
border-radius: 5px;
overflow: hidden;
margin-bottom: 36px;
background: #f6f6f6;
}
.heroImg {
object-fit: cover;
padding: 1.5%;
border-radius: 5px;
}
.sectionTitle {
font-size: clamp(28px, 3vw, 40px);
font-weight: 300;
margin-bottom: 18px;
}
.actions {
margin-top: 36px;
padding-top: 28px;
border-top: 1px solid #ececec;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 18px;
}
.related {
display: grid;
grid-template-columns: 154px minmax(0, 1fr);
gap: 18px;
padding: 18px;
border: 1px solid #eee;
border-radius: 5px;
transition: border-color 0.25s ease, transform 0.25s ease;
}
.related:hover {
border-color: #ccc;
transform: translateY(-2px);
}
.relatedMedia {
position: relative;
width: 154px;
height: 154px;
border-radius: 5px;
overflow: hidden;
background: #f6f6f6;
}
.relatedImg {
object-fit: cover;
}
.relatedBody {
display: flex;
flex-direction: column;
min-width: 0;
padding-top: 4px;
}
.relatedBody h3 {
font-size: 18px;
font-weight: 300;
margin-bottom: 10px;
}
.relatedBody p {
font-size: 14px;
font-weight: 300;
line-height: 1.45;
color: #9d9d9d;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
flex: 1;
}
.relatedMeta {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 14px;
font-size: 12px;
font-weight: 300;
color: var(--color-soft);
}
.relatedMeta span {
display: inline-flex;
align-items: center;
gap: 6px;
}
.relatedMeta img {
width: 14px;
height: 14px;
opacity: 0.55;
}
@media (max-width: 1100px) {
.layout {
grid-template-columns: 1fr;
}
.sidebar {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 720px) {
.sidebar {
grid-template-columns: 1fr;
}
.related {
grid-template-columns: 110px minmax(0, 1fr);
}
.relatedMedia {
width: 110px;
height: 110px;
}
}
@@ -0,0 +1,133 @@
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import DetailBanner from "@/components/DetailBanner";
import ArticleBody from "@/components/ArticleBody";
import ShareLikeActions from "@/components/ShareLikeActions";
import {
getAllBusinessSlugs,
getBusinessBlogPost,
getBusinessBySlug,
} from "@/data/business";
import styles from "./blog.module.css";
export function generateStaticParams() {
return getAllBusinessSlugs().flatMap((slug) => {
const business = getBusinessBySlug(slug);
return (business?.posts ?? []).map((post) => ({
slug,
postSlug: post.slug,
}));
});
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string; postSlug: string }>;
}) {
const { slug, postSlug } = await params;
const data = getBusinessBlogPost(slug, postSlug);
if (!data) return { title: "Blog Details | IranBay" };
return {
title: `${data.post.title} | Blog Details | IranBay`,
description: data.post.intro,
};
}
export default async function BlogDetailPage({
params,
}: {
params: Promise<{ slug: string; postSlug: string }>;
}) {
const { slug, postSlug } = await params;
const data = getBusinessBlogPost(slug, postSlug);
if (!data) notFound();
const { business, post, related } = data;
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<DetailBanner title="Blog Details" breadcrumbs={business.breadcrumbs} />
<div className={styles.layout}>
<article className={styles.article}>
<div className={styles.meta}>
<span>
<img src="/images/icons/calendar.svg" alt="" />
{post.date}
</span>
<span>
<img src="/images/icons/user.svg" alt="" />
{post.author}
</span>
<span>
<img src="/images/icons/category.svg" alt="" />
{post.category}
</span>
</div>
<h2 className={styles.title}>{post.title}.</h2>
<p className={styles.intro}>{post.intro}</p>
<div className={styles.hero}>
<Image
src={post.hero}
alt=""
fill
className={styles.heroImg}
sizes="(max-width: 1100px) 100vw, 1232px"
priority
/>
</div>
<h3 className={styles.sectionTitle}>{post.title}.</h3>
<ArticleBody blocks={post.body} />
<div className={styles.actions}>
<ShareLikeActions />
</div>
</article>
<aside className={styles.sidebar} aria-label="Related posts">
{related.map((item) => (
<Link
key={item.slug}
href={`/businesses/${business.slug}/blog/${item.slug}`}
className={styles.related}
>
<div className={styles.relatedMedia}>
<Image
src={item.image}
alt=""
fill
className={styles.relatedImg}
sizes="154px"
/>
</div>
<div className={styles.relatedBody}>
<h3>{item.title}</h3>
<p>{item.excerpt}</p>
<div className={styles.relatedMeta}>
<span>
<img src="/images/icons/calendar.svg" alt="" />
{item.date}
</span>
<span>
<img src="/images/icons/user.svg" alt="" />
{item.author}
</span>
</div>
</div>
</Link>
))}
</aside>
</div>
</main>
<Footer />
</div>
);
}
+9
View File
@@ -0,0 +1,9 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad);
}
+52
View File
@@ -0,0 +1,52 @@
import { notFound } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import BusinessDetailView from "@/components/BusinessDetail";
import {
getAllBusinessSlugs,
getBusinessBySlug,
} from "@/data/business";
import styles from "./page.module.css";
export function generateStaticParams() {
return getAllBusinessSlugs().map((slug) => ({ slug }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const business = getBusinessBySlug(slug);
if (!business) {
return { title: "Business | IranBay" };
}
return {
title: `${business.name} | IranBay`,
description: business.description.slice(0, 140),
};
}
export default async function BusinessDetailPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const business = getBusinessBySlug(slug);
if (!business) {
notFound();
}
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<BusinessDetailView business={business} />
</main>
<Footer />
</div>
);
}
@@ -0,0 +1,92 @@
import { notFound } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import DetailBanner from "@/components/DetailBanner";
import ArticleBody from "@/components/ArticleBody";
import ShareLikeActions from "@/components/ShareLikeActions";
import PortfolioGallery from "@/components/PortfolioGallery";
import {
getAllBusinessSlugs,
getBusinessBySlug,
getBusinessPortfolio,
} from "@/data/business";
import styles from "./portfolio.module.css";
export function generateStaticParams() {
return getAllBusinessSlugs().flatMap((slug) => {
const business = getBusinessBySlug(slug);
return (business?.portfolios ?? []).map((item) => ({
slug,
portfolioSlug: item.slug,
}));
});
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string; portfolioSlug: string }>;
}) {
const { slug, portfolioSlug } = await params;
const data = getBusinessPortfolio(slug, portfolioSlug);
if (!data) return { title: "Portfolios details | IranBay" };
return {
title: `${data.portfolio.title} | Portfolios details | IranBay`,
description: data.portfolio.description,
};
}
export default async function PortfolioDetailPage({
params,
}: {
params: Promise<{ slug: string; portfolioSlug: string }>;
}) {
const { slug, portfolioSlug } = await params;
const data = getBusinessPortfolio(slug, portfolioSlug);
if (!data) notFound();
const { business, portfolio } = data;
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<DetailBanner
title="Portfolios details"
breadcrumbs={business.breadcrumbs}
/>
<div className={styles.layout}>
<PortfolioGallery images={portfolio.images} />
<div className={styles.copy}>
<div className={styles.meta}>
<span>
<img src="/images/icons/calendar.svg" alt="" />
{portfolio.date}
</span>
<span>
<img src="/images/icons/user.svg" alt="" />
{portfolio.author}
</span>
<span>
<img src="/images/icons/category.svg" alt="" />
{portfolio.category}
</span>
</div>
<h2 className={styles.title}>{portfolio.title}.</h2>
<p className={styles.intro}>{portfolio.description}</p>
<hr className={styles.rule} />
<div className={styles.bodyWrap}>
<ArticleBody blocks={portfolio.body} />
</div>
<div className={styles.actions}>
<ShareLikeActions />
</div>
</div>
</div>
</main>
<Footer />
</div>
);
}
@@ -0,0 +1,82 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad) 80px;
}
.layout {
margin-top: 36px;
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
gap: 42px;
align-items: start;
}
.copy {
min-width: 0;
padding-top: 4px;
}
.meta {
display: flex;
flex-wrap: wrap;
gap: 18px 28px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 300;
color: var(--color-soft);
}
.meta span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.meta img {
width: 14px;
height: 14px;
opacity: 0.7;
object-fit: contain;
}
.title {
font-size: clamp(28px, 3vw, 40px);
font-weight: 300;
line-height: 1.2;
margin-bottom: 10px;
}
.intro {
font-size: 17px;
font-weight: 300;
line-height: 18px;
color: #9d9d9d;
margin-bottom: 18px;
}
.rule {
border: 0;
border-top: 1px solid #ececec;
margin: 0 0 20px;
}
.bodyWrap {
min-height: 280px;
}
.actions {
margin-top: 28px;
padding-top: 24px;
border-top: 1px solid #ececec;
}
@media (max-width: 980px) {
.layout {
grid-template-columns: 1fr;
}
}
+409
View File
@@ -0,0 +1,409 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad);
}
.bannerWrap {
padding-top: 28px;
}
.banner {
position: relative;
isolation: isolate;
overflow: hidden;
background: #f6f6f6;
border-radius: 5px;
min-height: 136px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 24px 20px;
}
.banner::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
opacity: 0.7;
background-image: var(--page-title-pattern);
}
.bannerTitle,
.crumbs {
position: relative;
z-index: 1;
}
.bannerTitle {
font-size: clamp(24px, 2.2vw, 35px);
font-weight: 400;
letter-spacing: 14px;
text-transform: uppercase;
}
.crumbs {
margin-top: 14px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
justify-content: center;
font-size: 12px;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--color-soft);
}
.crumbs > span {
display: inline-flex;
align-items: center;
gap: 10px;
}
.crumbChevron {
width: 10px;
height: 10px;
opacity: 0.55;
}
.crumbActive {
color: var(--color-black);
}
.container {
width: 100%;
}
.filtersBar {
margin-top: 28px;
background: #f9f9f9;
border-radius: 5px;
padding: 18px 24px;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 24px;
align-items: center;
}
.filtersLabel {
display: flex;
align-items: center;
gap: 12px;
min-width: 150px;
}
.filtersLabel strong {
display: block;
font-size: 18px;
font-weight: 500;
}
.filtersLabel span {
font-size: 12px;
color: var(--color-soft);
}
.filters {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
gap: 12px;
}
.filters input {
width: 100%;
border: 1px solid #e4e4e4;
border-radius: 4px;
padding: 12px 14px;
background: var(--color-white);
font: inherit;
font-size: 13px;
}
.filterBtn {
height: 44px;
padding: 0 22px;
font-size: 14px;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--color-soft);
}
.filterBtn img {
width: 10px;
height: 14px;
transition: filter 0.3s ease, opacity 0.3s ease;
}
:global(.filter-chip).filterBtn:hover,
:global(.filter-chip).filterBtn:focus-visible {
color: var(--color-white);
}
:global(.filter-chip).filterBtn:hover img,
:global(.filter-chip).filterBtn:focus-visible img {
filter: invert(1);
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 18px 0 28px;
}
.tag {
gap: 8px;
padding: 8px 12px;
font-size: 12px;
letter-spacing: 1px;
}
.tag button {
position: relative;
z-index: 2;
font-size: 14px;
line-height: 1;
color: var(--color-soft);
transition: color 0.3s ease;
}
:global(.filter-chip).tag:hover,
:global(.filter-chip).tag:focus-within {
color: var(--color-white);
}
:global(.filter-chip).tag:hover button,
:global(.filter-chip).tag:focus-within button {
color: var(--color-white);
}
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 20px;
}
.pager {
margin: 36px auto 56px;
display: flex;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
}
.pager a,
.pager span {
min-width: 34px;
height: 34px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
font-size: 13px;
color: var(--color-soft);
}
.pagerActive {
background: var(--color-card);
color: var(--color-black) !important;
}
.featureSection {
max-width: var(--container-width);
margin: 0 auto;
width: 100%;
}
.feature {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 32px;
align-items: center;
padding: 48px 0 64px;
}
.featureTitle {
font-size: 36px;
font-weight: 400;
letter-spacing: 0;
text-transform: uppercase;
line-height: 1.1;
}
.featureLead {
margin-top: 6px;
font-size: 22px;
font-weight: 400;
color: var(--color-soft);
}
.featureBody {
margin-top: 16px;
font-size: 15px;
font-weight: 400;
line-height: 1.4;
color: var(--color-body);
max-width: none;
}
.featureList {
list-style: none;
margin-top: 18px;
display: flex;
flex-direction: column;
gap: 10px;
}
.featureList li {
display: flex;
gap: 10px;
align-items: flex-start;
font-size: 15px;
color: var(--color-body);
}
.featureList img {
width: 9px;
height: 11px;
margin-top: 6px;
flex-shrink: 0;
}
.featureCta {
margin-top: 22px;
height: 34px;
padding: 0 16px;
font-size: 13px;
letter-spacing: 2.4px;
}
.featureVisual {
position: relative;
background: var(--color-card);
border-radius: 10px;
min-height: 360px;
aspect-ratio: 1 / 1.05;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 28px;
}
.featureVisual img {
width: auto;
height: auto;
max-width: 72%;
max-height: 86%;
object-fit: contain;
}
.exportSection {
margin: 0 0 72px;
}
.exportCta {
background: var(--color-card);
border-radius: 5px;
height: min(201px, calc((min(100vw, 1920px) - 2 * var(--page-pad)) * 201 / 1800));
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 0 40px 0 22%;
position: relative;
overflow: hidden;
}
.plane {
position: absolute;
left: 1.5%;
top: 50%;
width: min(240px, 16%);
height: auto;
max-height: 88%;
object-fit: contain;
transform: translateY(-52%) rotate(38deg);
transform-origin: center;
pointer-events: none;
}
.exportTitle {
font-size: clamp(28px, 4vw, 48px);
font-weight: 300;
letter-spacing: 8px;
text-transform: uppercase;
line-height: 1.05;
}
.beginBtn {
height: 40px;
padding: 0 18px;
gap: 10px;
font-size: 13px;
letter-spacing: 3px;
}
.beginBtn img {
width: 9px;
height: 16px;
}
@media (max-width: 1200px) {
.grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.filtersBar {
grid-template-columns: 1fr;
}
.filters {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 980px) {
.feature {
grid-template-columns: 1fr;
}
.exportCta {
flex-direction: column;
justify-content: center;
text-align: center;
height: auto;
min-height: 160px;
padding: 28px 20px 24px;
}
.plane {
position: relative;
left: auto;
top: auto;
width: min(180px, 46%);
max-height: 120px;
transform: rotate(38deg);
margin-bottom: 8px;
}
}
@media (max-width: 720px) {
.grid,
.filters {
grid-template-columns: 1fr;
}
.bannerTitle {
letter-spacing: 6px;
}
}
+162
View File
@@ -0,0 +1,162 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import ProducerCard from "@/components/ProducerCard";
import { aboutBullets, featuredProducers } from "@/data/home";
import {
activeFilterTags,
breadcrumbs,
businessFilters,
} from "@/data/pages";
import styles from "./businesses.module.css";
export const metadata = {
title: "Businesses | IranBay",
};
export default function BusinessesPage() {
const cards = [
...featuredProducers,
...featuredProducers,
...featuredProducers,
...featuredProducers,
].slice(0, 16);
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<section className={styles.bannerWrap}>
<div className={styles.banner}>
<h1 className={styles.bannerTitle}>Let’s explore</h1>
<nav className={styles.crumbs} aria-label="Breadcrumb">
{breadcrumbs.map((crumb, index) => (
<span key={crumb}>
{index > 0 ? (
<img
className={styles.crumbChevron}
src="/images/icons/chevron.svg"
alt=""
/>
) : null}
<span
className={
index === breadcrumbs.length - 1 ? styles.crumbActive : undefined
}
>
{crumb}
</span>
</span>
))}
</nav>
</div>
</section>
<section className={styles.container}>
<div className={styles.filtersBar}>
<div className={styles.filtersLabel}>
<img src="/images/icons/search.svg" alt="" width={22} height={22} />
<div>
<strong>Filters</strong>
<span>A better focus</span>
</div>
</div>
<div className={styles.filters}>
{businessFilters.map((filter) => (
<input
key={filter.label}
aria-label={filter.label}
defaultValue={filter.value}
/>
))}
<button className={`filter-chip ${styles.filterBtn}`} type="button">
Filter
<img src="/images/icons/chevron.svg" alt="" />
</button>
</div>
</div>
<div className={styles.tags}>
{activeFilterTags.map((tag) => (
<span className={`filter-chip ${styles.tag}`} key={tag}>
{tag}
<button type="button" aria-label={`Remove ${tag}`}>
×
</button>
</span>
))}
</div>
<div className={styles.grid}>
{cards.map((producer, index) => (
<ProducerCard
key={`${producer.name}-${index}`}
producer={producer}
/>
))}
</div>
<nav className={styles.pager} aria-label="Pagination">
<a href="/businesses">First</a>
<a href="/businesses">1</a>
<span className={styles.pagerActive}>2</span>
<a href="/businesses">3</a>
<span>…</span>
<a href="/businesses">15</a>
<a href="/businesses">Last</a>
</nav>
</section>
<section className={styles.featureSection}>
<div className={styles.feature}>
<div>
<h2 className={styles.featureTitle}>Processed foods,</h2>
<p className={styles.featureLead}>
We are a gate to Iran&apos;s market place
</p>
<p className={styles.featureBody}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
</p>
<ul className={styles.featureList}>
{aboutBullets.map((item, index) => (
<li key={`${item}-${index}`}>
<img src="/images/icons/bullet.svg" alt="" />
<span>{item}</span>
</li>
))}
</ul>
<a className={`outline-btn ${styles.featureCta}`} href="/about">
know more about us
</a>
</div>
<div className={styles.featureVisual}>
<img src="/images/hero/ice-cream.png" alt="Processed foods" />
</div>
</div>
</section>
<section className={styles.exportSection}>
<div className={`tile-hover ${styles.exportCta}`}>
<img
className={styles.plane}
src="/images/banners/plane.png"
alt=""
/>
<h2 className={styles.exportTitle}>
let’s
<br />
export
</h2>
<a className={`outline-btn ${styles.beginBtn}`} href="/contact">
begin
<img src="/images/icons/chevron.svg" alt="" width={9} height={16} />
</a>
</div>
</section>
</main>
<Footer />
</div>
);
}
+325
View File
@@ -0,0 +1,325 @@
.page {
background: var(--color-white);
}
.bannerWrap {
max-width: 1920px;
margin: 0 auto;
padding: 28px var(--page-pad) 0;
}
.banner {
position: relative;
isolation: isolate;
overflow: hidden;
background: #f6f6f6;
border-radius: 5px;
min-height: 136px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 28px 20px;
}
.banner::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
opacity: 0.7;
background-image: var(--page-title-pattern);
}
.bannerTitle,
.bannerSub {
position: relative;
z-index: 1;
}
.bannerTitle {
font-size: clamp(24px, 2.2vw, 35px);
font-weight: 400;
letter-spacing: 14px;
text-transform: uppercase;
}
.bannerSub {
margin-top: 8px;
font-size: 12px;
font-weight: 300;
letter-spacing: 4.02px;
text-transform: uppercase;
color: var(--color-soft);
}
.container {
max-width: 1552px;
margin: 0 auto;
padding: 0 var(--page-pad);
}
.panel {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(240px, 0.85fr);
gap: 64px;
padding: 56px 0 40px;
position: relative;
}
.panel::after {
content: "";
position: absolute;
top: 56px;
bottom: 40px;
left: 58%;
width: 2px;
background: var(--color-card);
}
.heading {
font-size: clamp(36px, 3.4vw, 55px);
font-weight: 700;
margin-bottom: 28px;
}
.sendLabel {
font-size: clamp(22px, 2vw, 35px);
font-weight: 400;
letter-spacing: 7px;
text-transform: uppercase;
margin-bottom: 10px;
}
.sendHint {
font-size: 12px;
font-weight: 300;
letter-spacing: 2.4px;
text-transform: uppercase;
color: #4b4b4b;
line-height: 1.5;
margin-bottom: 28px;
max-width: 520px;
}
.form {
display: grid;
gap: 18px;
max-width: 761px;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
}
.field {
position: relative;
display: block;
}
.field span {
position: absolute;
top: -8px;
left: 16px;
z-index: 1;
background: var(--color-white);
padding: 0 6px;
font-size: 12px;
font-weight: 300;
color: #aaa;
transition: color 0.25s ease;
}
.field input,
.field select,
.field textarea {
width: 100%;
border: 1px solid #eee;
border-radius: 5px;
background: var(--color-white);
padding: 14px 18px;
font: inherit;
font-size: 16px;
font-weight: 300;
color: var(--color-black);
outline: 0;
transition: border-color 0.25s ease;
}
.field:focus-within span {
color: var(--color-black);
}
.field input:focus,
.field select:focus,
.field textarea:focus {
border-color: var(--color-black);
}
.field select {
appearance: none;
background-image: url("/images/icons/chevron.svg");
background-repeat: no-repeat;
background-position: right 18px center;
background-size: 8px 14px;
padding-right: 36px;
}
.plain span {
display: none;
}
.field textarea {
min-height: 231px;
resize: vertical;
}
.formActions {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
.formActions .outline-btn,
.formActions button {
height: 29px;
min-width: 207px;
font-size: 12px;
letter-spacing: 2.4px;
}
.aside {
display: flex;
flex-direction: column;
gap: 36px;
padding-top: 8px;
}
.asideBlock h3 {
font-size: clamp(24px, 2vw, 35px);
font-weight: 600;
margin-bottom: 12px;
}
.asideBlock p,
.asideBlock a {
font-size: clamp(16px, 1.4vw, 25px);
font-weight: 300;
color: #4b4b4b;
line-height: 1.35;
}
.asideBlock p {
font-size: 18px;
}
.phones {
display: flex;
flex-direction: column;
gap: 2px;
}
.socials {
display: flex;
gap: 12px;
align-items: center;
}
.socials img {
width: 26px;
height: 26px;
object-fit: contain;
}
.mapWrap {
padding: 12px 0 56px;
}
.mapWrap img {
width: 100%;
height: clamp(220px, 32vw, 360px);
object-fit: cover;
border-radius: 4px;
}
.faq {
display: grid;
grid-template-columns: minmax(220px, 0.7fr) minmax(0, 1.3fr);
gap: 48px;
padding: 12px 0 96px;
}
.faqKicker {
font-size: 20px;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 12px;
}
.faqTitle {
font-size: clamp(34px, 3.4vw, 55px);
font-weight: 700;
line-height: 1.1;
}
.faqList {
display: flex;
flex-direction: column;
gap: 8px;
}
.faqItem {
padding: 12px 0 18px;
}
.faqTrigger {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
text-align: left;
font-size: clamp(20px, 1.8vw, 30px);
font-weight: 500;
}
.faqOpen {
transform: rotate(-90deg);
}
.faqClosed {
transform: rotate(90deg);
}
.faqAnswer {
margin-top: 14px;
font-size: 18px;
line-height: 1.35;
color: #939393;
max-width: 703px;
}
@media (max-width: 1100px) {
.panel::after {
display: none;
}
.panel,
.faq {
grid-template-columns: 1fr;
}
}
@media (max-width: 720px) {
.row {
grid-template-columns: 1fr;
}
.bannerTitle {
letter-spacing: 6px;
}
}
+137
View File
@@ -0,0 +1,137 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import ContactFaq from "@/components/ContactFaq";
import { contactInfo } from "@/data/pages";
import styles from "./contact.module.css";
export const metadata = {
title: "Contact Us | IranBay",
};
export default function ContactPage() {
return (
<div className={styles.page}>
<Header />
<main>
<section className={styles.bannerWrap}>
<div className={styles.banner}>
<h1 className={styles.bannerTitle}>Contact us page</h1>
<p className={styles.bannerSub}>
some of our most crowded categories
</p>
</div>
</section>
<section className={styles.container}>
<div className={styles.panel}>
<div>
<h2 className={styles.heading}>Get In Touch</h2>
<p className={styles.sendLabel}>Send message</p>
<p className={styles.sendHint}>
In publishing and graphic design, Lorem ipsum is a placeholder
text commonly used to.
</p>
<form className={styles.form}>
<div className={styles.row}>
<label className={styles.field}>
<span>Name</span>
<input name="name" defaultValue="Arad Charta" />
</label>
<label className={styles.field}>
<span>Family</span>
<input name="family" defaultValue="Arad Charta" />
</label>
</div>
<div className={styles.row}>
<label className={styles.field}>
<span>Interested in</span>
<select name="interest" defaultValue="Developer">
<option>Developer</option>
<option>Producer</option>
<option>Store</option>
<option>Service</option>
</select>
</label>
<label className={`${styles.field} ${styles.plain}`}>
<input
name="phone"
placeholder="Phone Number"
aria-label="Phone Number"
/>
</label>
</div>
<label className={styles.field}>
<span>Message</span>
<textarea name="message" placeholder="Description" />
</label>
<div className={styles.formActions}>
<button className="outline-btn" type="submit">
Submit message
</button>
</div>
</form>
</div>
<aside className={styles.aside}>
<div className={styles.asideBlock}>
<h3>Email Us :</h3>
<a href={`mailto:${contactInfo.email}`}>{contactInfo.email}</a>
</div>
<div className={styles.asideBlock}>
<h3>Call Us :</h3>
<div className={styles.phones}>
{contactInfo.phones.map((phone) => (
<p key={phone}>{phone}</p>
))}
</div>
</div>
<div className={styles.asideBlock}>
<h3>Follow :</h3>
<div className={styles.socials}>
<a href="https://instagram.com" aria-label="Instagram">
<img src="/images/icons/instagram.png" alt="" />
</a>
<a href="https://t.me" aria-label="Telegram">
<img src="/images/icons/telegram.png" alt="" />
</a>
<a href="https://facebook.com" aria-label="Facebook">
<img src="/images/icons/facebook.png" alt="" />
</a>
</div>
</div>
<div className={styles.asideBlock}>
<h3>Visit Us :</h3>
{contactInfo.addressLines.map((line) => (
<p key={line}>{line}</p>
))}
</div>
</aside>
</div>
</section>
<section className={styles.container}>
<div className={styles.mapWrap}>
<img src="/images/contact/map.png" alt="Office location map" />
</div>
</section>
<section className={styles.container}>
<div className={styles.faq}>
<div>
<p className={styles.faqKicker}>FAQ</p>
<h2 className={styles.faqTitle}>
Frequently
<br />
asked
<br />
questions.
</h2>
</div>
<ContactFaq />
</div>
</section>
</main>
<Footer />
</div>
);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+274
View File
@@ -0,0 +1,274 @@
:root {
--font-montserrat: "Montserrat", sans-serif;
--color-black: #000;
--color-white: #fff;
--color-bg: #ffffff;
--color-card: #eeeeee;
--color-muted: #4a4a4a;
--color-soft: #8e8e8e;
--color-body: #aaaaaa;
--color-dark: #1e1e1e;
--color-explore: #f9f9f9;
--color-footer: #070707;
--color-footer-text: #bababa;
--color-footer-line: #232323;
--page-pad: 60px;
--max-width: 1920px;
--container-width: 1080px;
--page-title-pattern:
repeating-linear-gradient(
135deg,
rgba(0, 0, 0, 0.045) 0 1px,
transparent 1px 16px
),
repeating-linear-gradient(
45deg,
rgba(0, 0, 0, 0.03) 0 1px,
transparent 1px 16px
);
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
height: 100%;
scroll-behavior: smooth;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
min-height: 100%;
color: var(--color-black);
background: var(--color-bg);
font-family: var(--font-montserrat), "Montserrat", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img {
display: block;
max-width: 100%;
}
button,
a {
font-family: inherit;
color: inherit;
}
a {
text-decoration: none;
color: inherit;
}
button {
background: none;
border: none;
cursor: pointer;
}
.icon {
display: block;
object-fit: contain;
}
.outline-btn {
position: relative;
isolation: isolate;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0.5px solid var(--color-black);
border-radius: 3px;
background: transparent;
color: var(--color-black);
font-weight: 300;
text-transform: uppercase;
letter-spacing: 0.2em;
white-space: nowrap;
transition: color 0.3s ease, border-color 0.3s ease;
}
.outline-btn:hover,
.outline-btn:focus-visible {
color: var(--color-white);
background: transparent;
}
.outline-btn:hover :is(span, strong, em, p, a, label, button),
.outline-btn:focus-visible :is(span, strong, em, p, a, label, button) {
color: var(--color-white);
}
.filter-chip {
position: relative;
isolation: isolate;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 33px;
padding: 6px 22px;
border: 0;
border-radius: 5px;
background: #f6f6f6;
color: var(--color-black);
font: inherit;
font-size: 18px;
font-weight: 300;
white-space: nowrap;
cursor: pointer;
transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease;
}
.filter-chip:hover,
.filter-chip:focus-visible {
color: var(--color-white);
}
.filter-chip:hover :is(span, strong, em, p, a, label, button),
.filter-chip:focus-visible :is(span, strong, em, p, a, label, button) {
color: var(--color-white);
}
.filter-chip.is-active {
background: var(--color-black);
color: var(--color-white);
}
.filter-chip.is-active:hover,
.filter-chip.is-active:focus-visible {
color: var(--color-black);
}
.filter-chip.is-active:hover :is(span, strong, em, p, a, label, button),
.filter-chip.is-active:focus-visible :is(span, strong, em, p, a, label, button) {
color: var(--color-black);
}
.tile-hover,
.outline-btn,
.filter-chip {
position: relative;
isolation: isolate;
}
.tile-hover::before,
.outline-btn::before,
.filter-chip::before {
content: "";
position: absolute;
inset: 0;
background: var(--color-black);
clip-path: inset(100% 0 0 0);
transition: clip-path 0.45s cubic-bezier(0.25, 0.8, 0.25, 1);
pointer-events: none;
border-radius: inherit;
}
/* Tiles: wipe sits above bare text; children are raised below. */
.tile-hover::before {
z-index: 0;
}
/*
Buttons/chips often use direct text nodes (no child wrappers).
Keep the wipe behind the label so white text stays visible.
*/
.outline-btn::before,
.filter-chip::before {
z-index: -1;
}
.filter-chip.is-active::before {
background: var(--color-white);
}
.tile-hover:hover::before,
.tile-hover:focus-within::before,
.outline-btn:hover::before,
.outline-btn:focus-visible::before,
.filter-chip:hover::before,
.filter-chip:focus-visible::before {
clip-path: inset(0 0 0 0);
}
.tile-hover > *:not(img),
.outline-btn > *,
.filter-chip > * {
position: relative;
z-index: 2;
}
.tile-hover:hover,
.tile-hover:focus-within,
.tile-hover:hover :is(h1, h2, h3, p, span, strong, a),
.tile-hover:focus-within :is(h1, h2, h3, p, span, strong, a) {
color: var(--color-white);
}
.tile-hover :is(h1, h2, h3, p, span, strong, a) {
transition: color 0.3s ease, border-color 0.3s ease;
}
.tile-hover:hover .outline-btn,
.tile-hover:hover .outline-btn:hover,
.tile-hover:focus-within .outline-btn {
color: var(--color-white);
border-color: var(--color-white);
background: transparent;
}
.tile-hover:hover .outline-btn::before,
.tile-hover:focus-within .outline-btn::before {
clip-path: inset(100% 0 0 0);
}
.outline-btn:hover img,
.outline-btn:focus-visible img,
.filter-chip:hover img,
.filter-chip:focus-visible img {
position: relative;
z-index: 2;
}
@media (prefers-reduced-motion: reduce) {
.tile-hover::before,
.outline-btn::before,
.filter-chip::before {
transition: none;
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
@media (max-width: 1100px) {
:root {
--page-pad: 32px;
}
}
@media (max-width: 720px) {
:root {
--page-pad: 20px;
}
}
+815
View File
@@ -0,0 +1,815 @@
.page {
background: var(--color-white);
}
.section {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 var(--page-pad);
scroll-margin-top: 80px;
}
.container {
max-width: 1080px;
}
.container + .container,
.section + .container {
margin-top: 96px;
}
.carousel {
margin-top: 96px;
padding-left: 0;
padding-right: 0;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.95fr) minmax(220px, 300px) minmax(220px, 300px);
gap: 7px;
align-items: stretch;
}
.heroCard {
position: relative;
background: var(--color-card);
border-radius: 5px;
min-height: 526px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 28px 24px 28px;
}
.heroMain {
padding: 0 72px 48px 102px;
}
.heroCopy {
position: relative;
z-index: 2;
max-width: 430px;
}
.heroTitle {
font-size: 40px;
font-weight: 300;
letter-spacing: 8px;
text-transform: uppercase;
line-height: 45px;
margin-bottom: 28px;
}
.heroTitleSmall {
font-size: 25px;
letter-spacing: 5px;
line-height: 30px;
margin-bottom: 16px;
}
.heroBtn {
height: 37px;
padding: 0 20px;
font-size: 16px;
letter-spacing: 3.2px;
}
.heroBtnSmall {
height: 29px;
padding: 0 16px;
font-size: 12px;
letter-spacing: 2.4px;
}
.heroImage {
position: absolute;
pointer-events: none;
}
.heroFurniture {
width: min(513px, 52%);
height: auto;
right: 4%;
top: 0;
mix-blend-mode: multiply;
}
.heroCard:hover .heroFurniture,
.heroCard:focus-within .heroFurniture {
mix-blend-mode: normal;
}
.heroCard:hover .heroNav img,
.heroCard:focus-within .heroNav img {
filter: invert(1);
}
.heroIceCream {
width: 84%;
height: auto;
left: 8%;
top: 14%;
}
.heroRug {
width: 81%;
height: auto;
left: 10%;
top: 16%;
}
/* Beat globals `.tile-hover > *:not(img) { position: relative }` so arrows stay overlaid */
.heroCard .heroNav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 29px;
height: 29px;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
}
.heroCard .heroNav img {
width: 11px;
height: 22px;
object-fit: contain;
}
.heroCard .heroPrev {
left: 16px;
transform: translateY(-50%) rotate(180deg);
}
.heroCard .heroNext {
right: 16px;
}
.join {
margin-top: 7px;
background: var(--color-card);
border-radius: 5px;
min-height: 136px;
display: flex;
align-items: center;
justify-content: center;
gap: 23px;
padding: 24px;
}
.joinIcon {
position: relative;
z-index: 2;
width: 62px;
height: 62px;
object-fit: contain;
}
.join:hover .joinIcon,
.join:focus-within .joinIcon {
filter: invert(1);
}
.joinCopy {
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
z-index: 1;
}
.joinTitle {
display: block;
font-size: 30px;
font-weight: 300;
letter-spacing: 6px;
text-transform: uppercase;
line-height: 30px;
}
.joinText {
display: block;
margin-top: 5px;
font-size: 14px;
font-weight: 300;
letter-spacing: 1.4px;
text-transform: uppercase;
color: var(--color-muted);
line-height: 18px;
}
.sectionHead {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
max-width: var(--container-width);
width: 100%;
margin: 0 auto 28px;
padding: 0 var(--page-pad);
}
.sectionHeadCentered {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin: 0 0 36px;
}
.arrows {
display: flex;
gap: 6px;
}
.arrowBtn {
width: 38px;
height: 36px;
background: var(--color-card);
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.arrowBtn img {
width: 8px;
height: 14px;
object-fit: contain;
position: relative;
z-index: 2;
transition: filter 0.3s ease;
}
.arrowBtn:global(.tile-hover):hover img,
.arrowBtn:global(.tile-hover):focus-visible img {
filter: brightness(0) invert(1);
}
.arrowPrev {
transform: rotate(180deg);
}
.sectionTitles {
text-align: center;
}
.sectionTitle {
font-size: 20px;
font-weight: 400;
letter-spacing: 6px;
text-transform: uppercase;
white-space: nowrap;
}
.sectionSubtitle {
margin-top: 6px;
font-size: 11px;
font-weight: 300;
letter-spacing: 3.2px;
text-transform: uppercase;
}
.explore {
height: 36px;
min-width: 180px;
background: var(--color-explore);
border-radius: 5px;
display: inline-flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
font-size: 13px;
font-weight: 300;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--color-soft);
overflow: hidden;
}
.explore img {
width: 16px;
height: 16px;
position: relative;
z-index: 2;
transition: filter 0.3s ease;
}
.explore:global(.tile-hover):hover img,
.explore:global(.tile-hover):focus-visible img {
filter: brightness(0) invert(1);
}
.categoryGrid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 28px 20px;
}
.categoryCard {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding-top: 38%;
overflow: visible;
}
.categoryStage {
width: 100%;
aspect-ratio: 430 / 187;
background: var(--color-card);
border-radius: 5px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding: 0 8px 10px;
overflow: visible;
}
.categoryImage {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 58%;
height: 120%;
bottom: 36%;
object-fit: contain;
pointer-events: none;
}
.industry {
width: 59%;
height: 135%;
bottom: 33%;
}
.food {
width: 50%;
height: 114%;
bottom: 45%;
}
.fruits {
width: 63%;
height: 144%;
bottom: 33%;
}
.clothes {
width: 58%;
height: 148%;
bottom: 32%;
}
.chair {
width: 43%;
height: 137%;
bottom: 45%;
}
.shoes {
width: 79%;
height: 96%;
bottom: 44%;
}
.washer {
width: 58%;
height: 133%;
bottom: 44%;
}
.services {
width: 69%;
height: 159%;
bottom: 26%;
}
.categoryTitle {
position: relative;
z-index: 1;
font-size: 12px;
font-weight: 400;
letter-spacing: 3px;
text-transform: uppercase;
line-height: 1.2;
}
.categoryCopy {
position: relative;
z-index: 1;
margin-top: 4px;
font-size: 10px;
font-weight: 300;
color: var(--color-muted);
}
.producerGrid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 36px;
align-items: start;
max-width: var(--max-width);
margin: 0 auto;
padding: 0 var(--page-pad);
}
.about {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 32px;
align-items: center;
padding: 48px 0 8px;
overflow: visible;
}
.aboutKicker {
font-size: 36px;
font-weight: 400;
text-transform: uppercase;
line-height: 1.1;
}
.aboutKicker span {
font-size: 24px;
text-transform: none;
}
.aboutLead {
margin-top: 6px;
font-size: 22px;
font-weight: 400;
color: var(--color-soft);
}
.aboutBody {
margin-top: 16px;
font-size: 15px;
font-weight: 400;
color: var(--color-body);
line-height: 1.4;
}
.aboutList {
list-style: none;
margin-top: 18px;
display: flex;
flex-direction: column;
gap: 10px;
}
.aboutList li {
display: flex;
gap: 10px;
align-items: flex-start;
font-size: 15px;
color: var(--color-body);
}
.aboutList img {
width: 9px;
height: 11px;
margin-top: 6px;
flex-shrink: 0;
}
.aboutBtn {
margin-top: 22px;
height: 34px;
padding: 0 16px;
font-size: 13px;
letter-spacing: 2.4px;
}
.aboutVisual {
position: relative;
width: 100%;
aspect-ratio: 837 / 639;
overflow: visible;
}
.aboutFrame {
position: absolute;
left: 0;
bottom: 0;
width: 58.8%;
height: 86%;
background: var(--color-card);
border-radius: 10px;
}
.aboutShip {
position: absolute;
top: 0;
right: 0;
width: 94.5%;
height: 100%;
object-fit: cover;
object-position: 55% 35%;
}
.bannerBlock {
margin-top: 96px;
overflow: visible;
padding-bottom: clamp(40px, 7vw, 110px);
}
.bannerRow {
display: grid;
grid-template-columns: minmax(0, 2.92fr) repeat(3, minmax(0, 1fr));
gap: 7px;
align-items: end;
padding-top: 96px;
}
.bannerCard {
position: relative;
background: var(--color-card);
border-radius: 5px;
min-height: 360px;
padding: 24px 18px 24px;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 14px;
overflow: visible;
}
.bannerWide {
min-height: 360px;
padding-left: 48px;
}
.bannerImage {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: min(280px, 56%);
bottom: 118px;
object-fit: contain;
pointer-events: none;
}
.tv {
width: min(320px, 48%);
bottom: 130px;
left: 42%;
}
.pistachio {
width: 130%;
max-width: 260px;
bottom: 108px;
transform: translateX(-50%) rotate(18deg);
}
.shampoo {
width: 58%;
height: auto;
bottom: 115px;
}
.saffron {
width: 54%;
bottom: 115px;
}
.bannerTitle {
position: relative;
z-index: 1;
font-size: 18px;
font-weight: 300;
letter-spacing: 3.5px;
text-transform: uppercase;
line-height: 1.2;
margin: 0;
}
.bannerWide .bannerTitle {
font-size: 32px;
letter-spacing: 6px;
line-height: 1.15;
}
.bannerBtn {
position: relative;
z-index: 1;
align-self: flex-start;
height: 29px;
padding: 0 14px;
font-size: 12px;
letter-spacing: 2.2px;
}
.bannerBtnWide {
height: 34px;
padding: 0 18px;
font-size: 14px;
letter-spacing: 2.4px;
}
.exportCta {
position: relative;
width: 100%;
margin-top: 7px;
background: var(--color-card);
border-radius: 5px;
height: min(201px, calc((min(100vw, var(--max-width)) - 2 * var(--page-pad)) * 201 / 1800));
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 0 40px 0 22%;
overflow: visible;
}
.plane {
position: absolute;
left: 0;
top: 50%;
width: min(220px, 13.5%);
height: auto;
max-width: none;
object-fit: contain;
transform: translate(-6%, -58%) rotate(42deg);
transform-origin: center;
pointer-events: none;
}
.exportTitle {
position: relative;
z-index: 2;
font-size: 32px;
font-weight: 300;
letter-spacing: 6px;
text-transform: uppercase;
line-height: 1.15;
}
.beginBtn {
position: relative;
z-index: 2;
height: 34px;
min-width: 320px;
padding: 0 16px;
font-size: 14px;
letter-spacing: 2.8px;
justify-content: space-between;
}
.beginBtn img {
width: 9px;
height: 16px;
object-fit: contain;
}
.exportCta:hover .beginBtn img,
.exportCta:focus-within .beginBtn img {
filter: invert(1);
}
@media (max-width: 1400px) {
.sectionTitle {
letter-spacing: 4px;
font-size: 18px;
}
.heroTitle {
font-size: 28px;
letter-spacing: 5px;
line-height: 1.15;
}
.aboutKicker {
font-size: 30px;
}
}
@media (max-width: 1100px) {
.hero,
.bannerRow {
grid-template-columns: 1fr;
}
.heroCard {
min-height: 420px;
}
.heroMain {
padding: 48px 24px 32px;
}
.heroFurniture,
.heroIceCream,
.heroRug {
width: min(280px, 46%);
}
.categoryGrid,
.producerGrid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.about {
grid-template-columns: 1fr;
margin-top: 0;
}
.aboutVisual {
min-height: unset;
max-width: 420px;
margin: 0 auto;
order: -1;
}
.sectionHead {
grid-template-columns: 1fr;
justify-items: center;
text-align: center;
}
.exportCta {
flex-direction: column;
justify-content: center;
text-align: center;
padding: 24px 20px 24px;
height: auto;
min-height: 140px;
}
.plane {
left: 50%;
top: -8%;
width: min(180px, 42%);
transform: translate(-50%, -40%) rotate(42deg);
}
.beginBtn {
width: 100%;
min-width: 0;
}
}
@media (max-width: 720px) {
.heroCard {
min-height: 360px;
padding: 20px;
}
.heroMain {
padding: 32px 20px 24px;
}
.heroTitle,
.joinTitle,
.aboutKicker {
font-size: 24px;
letter-spacing: 3px;
line-height: 1.2;
}
.sectionTitle {
font-size: 15px;
letter-spacing: 2.5px;
white-space: normal;
}
.heroFurniture,
.heroIceCream,
.heroRug {
width: 42%;
opacity: 0.95;
}
.join {
flex-direction: column;
text-align: center;
}
.joinCopy {
align-items: center;
}
.categoryGrid,
.producerGrid {
grid-template-columns: 1fr;
}
.explore {
width: 100%;
min-width: 0;
}
.categoryCard {
padding-top: 28%;
max-width: 280px;
margin: 0 auto;
}
}
+23
View File
@@ -0,0 +1,23 @@
import type { Metadata } from "next";
import { Montserrat } from "next/font/google";
import "./globals.css";
const montserrat = Montserrat({
subsets: ["latin"],
weight: ["300", "400", "500", "600"],
variable: "--font-montserrat",
display: "swap",
});
export const metadata: Metadata = {
title: "IranBay | A gate to iran market",
description: "IranBay homepage — a gate to Iran’s marketplace.",
};
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en" dir="ltr" className={montserrat.variable}>
<body className={montserrat.className}>{children}</body>
</html>
);
}
+451
View File
@@ -0,0 +1,451 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import ProducerCard from "@/components/ProducerCard";
import {
aboutBullets,
featuredCategories,
featuredProducers,
type Producer,
} from "@/data/home";
import {
getWebsiteHomeMedia,
type WebsiteMediaItem,
} from "@/lib/website-media";
import {
carouselBusinessToProducer,
getWebsiteCarousels,
type WebsiteCarousel,
} from "@/lib/website-carousels";
import styles from "./home.module.css";
function SectionHeading({
title,
subtitle,
exploreHref,
}: {
title: string;
subtitle?: string;
exploreHref: string;
}) {
return (
<div className={styles.sectionHead}>
<div className={styles.arrows}>
<button
className={`tile-hover ${styles.arrowBtn}`}
type="button"
aria-label="Previous"
>
<img
className={styles.arrowPrev}
src="/images/icons/chevron.svg"
alt=""
width={8}
height={14}
/>
</button>
<button
className={`tile-hover ${styles.arrowBtn}`}
type="button"
aria-label="Next"
>
<img src="/images/icons/chevron.svg" alt="" width={8} height={14} />
</button>
</div>
<div className={styles.sectionTitles}>
<h2 className={styles.sectionTitle}>{title}</h2>
<p className={styles.sectionSubtitle}>
{subtitle || "some of our most crowded categories"}
</p>
</div>
<a className={`tile-hover ${styles.explore}`} href={exploreHref}>
Explore all
<img src="/images/icons/menu.svg" alt="" width={22} height={22} />
</a>
</div>
);
}
function BusinessCarouselSection({
carousel,
fallbackProducers,
}: {
carousel?: WebsiteCarousel;
fallbackProducers?: Producer[];
}) {
if (!carousel) return null;
const producers =
carousel.businesses.length > 0
? carousel.businesses.map(carouselBusinessToProducer)
: (fallbackProducers ?? []);
if (!producers.length) return null;
return (
<section
className={`${styles.section} ${styles.carousel}`}
id={carousel.slug || carousel.id}
>
<SectionHeading
title={carousel.title}
subtitle={carousel.subtext || undefined}
exploreHref="/businesses"
/>
<div className={styles.producerGrid}>
{producers.map((producer) => (
<ProducerCard
key={`${carousel.id}-${producer.slug}`}
producer={producer}
/>
))}
</div>
</section>
);
}
function TitleLines({ text }: { text: string }) {
const lines = text.includes("\n")
? text.split("\n")
: (() => {
const parts = text.trim().split(/\s+/);
if (parts.length <= 1) return [text];
return [parts[0], parts.slice(1).join(" ")];
})();
return (
<>
{lines.map((line, index) => (
<span key={`${line}-${index}`}>
{index > 0 ? <br /> : null}
{line}
</span>
))}
</>
);
}
function fallbackMedia() {
return {
slider: [
{
id: "slider-1",
imageUrl: "/images/hero/furniture.png",
linkUrl: "#producers",
title: "Iranian modern art",
cta: "browse furniture producers",
},
] satisfies WebsiteMediaItem[],
standingBanners: [
{
id: "standing-1",
imageUrl: "/images/hero/ice-cream.png",
linkUrl: "#producers",
title: "food industry",
cta: "browse producers",
},
{
id: "standing-2",
imageUrl: "/images/hero/rug.png",
linkUrl: "#producers",
title: "original & pure art",
cta: "browse producers",
},
] satisfies WebsiteMediaItem[],
categories: featuredCategories.map((category, index) => ({
id: `cat-${index + 1}`,
imageUrl: category.image,
linkUrl: "#producers",
title: category.title,
subtitle: category.description,
})),
menuImages: [],
aboutUs: {
id: "about-1",
imageUrl: "/images/banners/ship.png",
linkUrl: "/about",
} satisfies WebsiteMediaItem,
banners: [
{
id: "banner-1",
imageUrl: "/images/banners/tv.png",
linkUrl: "#producers",
title: "Iranian modern art",
cta: "browse furniture producers",
},
{
id: "banner-2",
imageUrl: "/images/banners/pistachio.png",
linkUrl: "#producers",
title: "food industry",
cta: "browse producers",
},
{
id: "banner-3",
imageUrl: "/images/banners/shampoo.png",
linkUrl: "#producers",
title: "food industry",
cta: "browse producers",
},
{
id: "banner-4",
imageUrl: "/images/banners/saffron.png",
linkUrl: "#producers",
title: "original & pure art",
cta: "browse producers",
},
{
id: "banner-5",
imageUrl: "/images/banners/plane.png",
linkUrl: "/register",
title: "let's export",
cta: "begin",
},
] satisfies WebsiteMediaItem[],
};
}
export default async function Home() {
const [mediaResult, carousels] = await Promise.all([
getWebsiteHomeMedia(),
getWebsiteCarousels(),
]);
const media = mediaResult ?? fallbackMedia();
const slide = media.slider[0];
const standing = media.standingBanners;
const collectionBanners = media.banners.slice(0, 4);
const exportBanner =
media.banners[4] ?? media.banners[media.banners.length - 1];
const [firstCarousel, secondCarousel, ...restCarousels] = carousels;
return (
<div className={styles.page} id="top">
<Header />
<main>
<section className={styles.section} aria-label="Featured collections">
<div className={styles.hero}>
<article className={`tile-hover ${styles.heroCard} ${styles.heroMain}`}>
<button
className={`${styles.heroNav} ${styles.heroPrev}`}
type="button"
aria-label="Previous slide"
>
<img src="/images/icons/chevron.svg" alt="" width={11} height={22} />
</button>
<div className={styles.heroCopy}>
<h1 className={styles.heroTitle}>
<TitleLines text={slide?.title || "Iranian modern art"} />
</h1>
<a
className={`outline-btn ${styles.heroBtn}`}
href={slide?.linkUrl || "#producers"}
>
{slide?.cta || "browse furniture producers"}
</a>
</div>
<img
className={`${styles.heroImage} ${styles.heroFurniture}`}
src={slide?.imageUrl || "/images/hero/furniture.png"}
alt={slide?.title || "Iranian modern furniture"}
/>
<button
className={`${styles.heroNav} ${styles.heroNext}`}
type="button"
aria-label="Next slide"
>
<img src="/images/icons/chevron.svg" alt="" width={11} height={22} />
</button>
</article>
{standing.slice(0, 2).map((banner, index) => (
<article key={banner.id} className={`tile-hover ${styles.heroCard}`}>
<img
className={`${styles.heroImage} ${
index === 0 ? styles.heroIceCream : styles.heroRug
}`}
src={banner.imageUrl}
alt={banner.title || ""}
/>
<h2 className={`${styles.heroTitle} ${styles.heroTitleSmall}`}>
<TitleLines text={banner.title || "Browse"} />
</h2>
<a
className={`outline-btn ${styles.heroBtnSmall}`}
href={banner.linkUrl || "#producers"}
>
{banner.cta || "browse producers"}
</a>
</article>
))}
</div>
</section>
<section className={styles.section} id="join">
<a href="/register" className={`tile-hover ${styles.join}`}>
<img
className={styles.joinIcon}
src="/images/icons/join-add.svg"
alt=""
width={62}
height={62}
/>
<span className={styles.joinCopy}>
<span className={styles.joinTitle}>Join our club</span>
<span className={styles.joinText}>
we will show your business to verity
<br />
of visitor around the world
</span>
</span>
</a>
</section>
<section className={`${styles.section} ${styles.container}`} id="categories">
<div className={styles.sectionHeadCentered}>
<h2 className={styles.sectionTitle}>Featured categories</h2>
<p className={styles.sectionSubtitle}>
some of our most crowded categories
</p>
</div>
<div className={styles.categoryGrid}>
{media.categories.map((category) => (
<a
key={category.id}
href={category.linkUrl || "#producers"}
className={styles.categoryCard}
>
<div className={`tile-hover ${styles.categoryStage}`}>
<img
className={styles.categoryImage}
src={category.imageUrl}
alt=""
/>
<h3 className={styles.categoryTitle}>
{category.title || "Category"}
</h3>
<p className={styles.categoryCopy}>
{category.subtitle || ""}
</p>
</div>
</a>
))}
</div>
</section>
<BusinessCarouselSection
carousel={firstCarousel}
fallbackProducers={featuredProducers}
/>
<section className={`${styles.section} ${styles.container}`} id="about">
<div className={styles.about}>
<div>
<h2 className={styles.aboutKicker}>
IranBay<span>,</span>
</h2>
<p className={styles.aboutLead}>
We are a gate to iran’s market place
</p>
<p className={styles.aboutBody}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<ul className={styles.aboutList}>
{aboutBullets.map((item, index) => (
<li key={`${item}-${index}`}>
<img src="/images/icons/bullet.svg" alt="" width={11} height={11} />
<span>{item}</span>
</li>
))}
</ul>
<a
className={`outline-btn ${styles.aboutBtn}`}
href={media.aboutUs.linkUrl || "/about"}
>
know more about us
</a>
</div>
<div className={styles.aboutVisual}>
<div className={styles.aboutFrame} />
<img
className={styles.aboutShip}
src={media.aboutUs.imageUrl}
alt="Cargo ship entering the market"
/>
</div>
</div>
</section>
<BusinessCarouselSection
carousel={secondCarousel}
fallbackProducers={featuredProducers}
/>
<section className={`${styles.section} ${styles.bannerBlock}`} aria-label="Browse collections">
<div className={styles.bannerRow}>
{collectionBanners.map((banner, index) => (
<article
key={banner.id}
className={`tile-hover ${styles.bannerCard} ${
index === 0 ? styles.bannerWide : ""
}`}
>
<img
className={styles.bannerImage}
src={banner.imageUrl}
alt={banner.title || ""}
/>
<h2 className={styles.bannerTitle}>
<TitleLines text={banner.title || "Browse"} />
</h2>
<a
className={`outline-btn ${styles.bannerBtn} ${
index === 0 ? styles.bannerBtnWide : ""
}`}
href={banner.linkUrl || "#producers"}
>
{banner.cta || "browse producers"}
</a>
</article>
))}
</div>
{exportBanner ? (
<div className={`tile-hover ${styles.exportCta}`}>
<img
className={styles.plane}
src={exportBanner.imageUrl}
alt=""
/>
<h2 className={styles.exportTitle}>
<TitleLines text={exportBanner.title || "let's export"} />
</h2>
<a
className={`outline-btn ${styles.beginBtn}`}
href={exportBanner.linkUrl || "/register"}
>
{exportBanner.cta || "begin"}
<img src="/images/icons/chevron.svg" alt="" width={9} height={16} />
</a>
</div>
) : null}
</section>
{restCarousels.map((carousel, index) => (
<BusinessCarouselSection
key={carousel.id}
carousel={carousel}
fallbackProducers={index === 0 ? featuredProducers : undefined}
/>
))}
</main>
<Footer />
</div>
);
}
+9
View File
@@ -0,0 +1,9 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad);
}
+24
View File
@@ -0,0 +1,24 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import BusinessRegistration from "@/components/BusinessRegistration";
import RequireAuth from "@/components/RequireAuth";
import styles from "./page.module.css";
export const metadata = {
title: "Business registration | IranBay",
description: "Register your business on IranBay in three steps.",
};
export default function RegisterPage() {
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<RequireAuth returnPath="/register">
<BusinessRegistration />
</RequireAuth>
</main>
<Footer />
</div>
);
}
+65
View File
@@ -0,0 +1,65 @@
.page {
background: var(--color-white);
min-height: 100vh;
}
.container {
max-width: var(--container-width);
margin: 0 auto;
padding: 0 var(--page-pad);
}
.sectionTitle {
font-size: 26px;
font-weight: 400;
letter-spacing: 10px;
text-transform: uppercase;
text-align: center;
}
.sectionSubtitle {
margin-top: 6px;
font-size: 11px;
font-weight: 300;
letter-spacing: 3.2px;
text-transform: uppercase;
text-align: center;
color: var(--color-soft);
}
.outlineBtn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
padding: 0 18px;
border: 0.5px solid var(--color-black);
border-radius: 3px;
font-size: 12px;
font-weight: 300;
letter-spacing: 2.4px;
text-transform: uppercase;
}
.pageBanner {
background: var(--color-card);
padding: 48px var(--page-pad);
text-align: center;
margin-bottom: 48px;
}
.pageBannerTitle {
font-size: 28px;
font-weight: 400;
letter-spacing: 10px;
text-transform: uppercase;
}
.pageBannerSub {
margin-top: 8px;
font-size: 11px;
font-weight: 300;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--color-soft);
}