Improve mobile storefront layout, navigation, and product browsing.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b3df0d3eb4
commit
4e4dd731b2
@@ -32,7 +32,7 @@ export default async function AboutPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductListHero
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import SiteHeader from "@/components/home/SiteHeader";
|
||||
import SiteFooter from "@/components/home/SiteFooter";
|
||||
import PartnerCta from "@/components/home/PartnerCta";
|
||||
import ProductListHero from "@/components/products/ProductListHero";
|
||||
import { mapHomeSlots } from "@/lib/map-home-slots";
|
||||
import { fetchPublicWebsiteSlots } from "@/lib/website-slots";
|
||||
import {
|
||||
fetchPublicCategories,
|
||||
mapCategoriesToMegaTabs,
|
||||
} from "@/lib/categories-api";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "سبد خرید | مانا کالکشن",
|
||||
description: "سبد خرید مانا کالکشن",
|
||||
};
|
||||
|
||||
export default async function CartPage() {
|
||||
const [slotItems, apiCategories] = await Promise.all([
|
||||
fetchPublicWebsiteSlots(),
|
||||
fetchPublicCategories(),
|
||||
]);
|
||||
const home = mapHomeSlots(slotItems);
|
||||
const megaTabs = mapCategoriesToMegaTabs(
|
||||
apiCategories,
|
||||
home.megaTabs.map((t) => ({ image: t.image })),
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductListHero
|
||||
titleFa="سبد خرید"
|
||||
titleEn="Cart"
|
||||
heroImage={home.heroSlides[0]?.image || "/images/home/hero/1.jpg"}
|
||||
breadcrumbs={[
|
||||
{ label: "خانه", href: "/" },
|
||||
{ label: "سبد خرید" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section className="mx-auto max-w-[1200px] px-4 py-16 text-center sm:px-6 sm:py-20 lg:px-8">
|
||||
<p className="font-doran text-xl font-medium text-ink sm:text-2xl">
|
||||
سبد خرید شما خالی است
|
||||
</p>
|
||||
<p className="mx-auto mt-3 max-w-md text-sm font-light leading-7 text-muted sm:text-base">
|
||||
محصولی به سبد اضافه نشده است. از کالکشنها دیدن کنید و انتخاب خود را
|
||||
شروع کنید.
|
||||
</p>
|
||||
<Link
|
||||
href="/products"
|
||||
className="mt-8 inline-flex h-11 items-center justify-center rounded-[4px] border border-ink bg-ink px-8 text-sm font-light text-white transition-opacity hover:opacity-85"
|
||||
>
|
||||
مشاهده محصولات
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
<PartnerCta />
|
||||
<SiteFooter paymentBadges={home.paymentBadges} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -174,7 +174,7 @@ export default async function CategoryPage({ params, searchParams }: PageProps)
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductListHero
|
||||
|
||||
@@ -31,7 +31,7 @@ export default async function CategoriesIndexPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductListHero
|
||||
|
||||
@@ -30,7 +30,7 @@ export default async function ContactPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductListHero
|
||||
|
||||
+390
-1
@@ -1,7 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--background: #f5f5f5;
|
||||
--foreground: #0a0a0a;
|
||||
--surface: #ffffff;
|
||||
--muted: #6b6b6b;
|
||||
@@ -235,6 +235,395 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* Sticky close-up: scroll pulls back, then copy + button appear over the image. */
|
||||
.opening-track {
|
||||
view-timeline-name: --opening;
|
||||
view-timeline-axis: block;
|
||||
}
|
||||
|
||||
.opening-media,
|
||||
.opening-shade,
|
||||
.opening-headline,
|
||||
.opening-copy {
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: both;
|
||||
animation-timeline: --opening;
|
||||
animation-duration: auto;
|
||||
}
|
||||
|
||||
.opening-media {
|
||||
animation-name: opening-media;
|
||||
animation-range: cover 0% cover 68%;
|
||||
}
|
||||
|
||||
.opening-headline {
|
||||
animation-name: opening-headline;
|
||||
animation-range: cover 42% cover 56%;
|
||||
}
|
||||
|
||||
.opening-shade {
|
||||
animation-name: opening-shade;
|
||||
animation-range: cover 46% cover 64%;
|
||||
}
|
||||
|
||||
.opening-copy {
|
||||
animation-name: opening-copy, opening-copy-hit;
|
||||
animation-timing-function: linear, step-end;
|
||||
animation-range: cover 52% cover 70%, cover 52% cover 70%;
|
||||
}
|
||||
|
||||
@keyframes opening-media {
|
||||
from {
|
||||
transform: scale(1.38);
|
||||
}
|
||||
to {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes opening-headline {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes opening-shade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes opening-copy {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 28px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes opening-copy-hit {
|
||||
0%,
|
||||
65% {
|
||||
pointer-events: none;
|
||||
}
|
||||
100% {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@supports not (animation-timeline: view()) {
|
||||
.opening-track {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.opening-stage {
|
||||
position: relative;
|
||||
top: auto;
|
||||
height: calc(100dvh - 4.5rem);
|
||||
}
|
||||
|
||||
.opening-media,
|
||||
.opening-shade,
|
||||
.opening-headline,
|
||||
.opening-copy {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.opening-media {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.opening-shade {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.opening-headline {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.opening-copy {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.opening-track {
|
||||
height: auto;
|
||||
view-timeline-name: none;
|
||||
}
|
||||
|
||||
.opening-stage {
|
||||
position: relative;
|
||||
top: auto;
|
||||
height: min(86dvh, 920px);
|
||||
}
|
||||
|
||||
.opening-media,
|
||||
.opening-shade,
|
||||
.opening-headline,
|
||||
.opening-copy {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.opening-media {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.opening-shade {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.opening-headline {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.opening-copy {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Four-image sticky story (Ayoubi-style): scroll advances full-bleed slides. */
|
||||
.sticky-story {
|
||||
--story: 0;
|
||||
--story-header-h: 4rem;
|
||||
--frame-inset: 1.6rem;
|
||||
--story-accent: #e8e2d8;
|
||||
height: calc((100dvh - var(--story-header-h)) * 4);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sticky-story {
|
||||
--story-header-h: 4.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sticky-story__stage {
|
||||
position: sticky;
|
||||
top: var(--story-header-h);
|
||||
height: calc(100dvh - var(--story-header-h));
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.sticky-story__slide {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sticky-story__slide.is-under {
|
||||
opacity: 1;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.sticky-story__slide.is-under .sticky-story__copy {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sticky-story__slide.is-on {
|
||||
z-index: 1;
|
||||
opacity: 1;
|
||||
animation: sticky-story-fade 0.8s ease;
|
||||
}
|
||||
|
||||
.sticky-story__media,
|
||||
.sticky-story__shade,
|
||||
.sticky-story__copy {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.sticky-story__media {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sticky-story__shade {
|
||||
background:
|
||||
linear-gradient(
|
||||
to left,
|
||||
rgba(7, 7, 8, 0.82) 0%,
|
||||
rgba(7, 7, 8, 0.48) 38%,
|
||||
rgba(7, 7, 8, 0.18) 68%
|
||||
),
|
||||
linear-gradient(to top, rgba(7, 7, 8, 0.45) 0%, transparent 42%),
|
||||
linear-gradient(to right, rgba(7, 7, 8, 0.35), transparent 30%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sticky-story__plate {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: var(--frame-inset);
|
||||
border: 1px solid var(--story-accent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sticky-story__copy {
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 0.85rem;
|
||||
max-width: 38rem;
|
||||
padding: 2rem clamp(2rem, 7vw, 6rem);
|
||||
text-align: start;
|
||||
color: #f3eee6;
|
||||
pointer-events: none;
|
||||
text-shadow: 0 2px 24px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.sticky-story__slide.is-on .sticky-story__copy {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.sticky-story__cta {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.sticky-story__kicker {
|
||||
margin: 0;
|
||||
font-family: var(--font-montserrat), system-ui, sans-serif;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(243, 238, 230, 0.72);
|
||||
}
|
||||
|
||||
.sticky-story__copy h2 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.1rem, 5vw, 3.5rem);
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
color: #f5f5f7;
|
||||
}
|
||||
|
||||
.sticky-story__lead {
|
||||
margin: 0;
|
||||
max-width: 24rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.8;
|
||||
color: rgba(243, 238, 230, 0.86);
|
||||
}
|
||||
|
||||
.sticky-story__rail {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
inset-inline-end: calc(var(--frame-inset) + 1.15rem);
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sticky-story__rail li {
|
||||
font-family: var(--font-montserrat), system-ui, sans-serif;
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.16em;
|
||||
color: rgba(243, 238, 230, 0.45);
|
||||
}
|
||||
|
||||
.sticky-story__rail li.is-on {
|
||||
color: var(--story-accent);
|
||||
}
|
||||
|
||||
@keyframes sticky-story-fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.sticky-story {
|
||||
--frame-inset: 0.75rem;
|
||||
}
|
||||
|
||||
.sticky-story__shade {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(7, 7, 8, 0.9) 0%,
|
||||
rgba(7, 7, 8, 0.55) 34%,
|
||||
rgba(7, 7, 8, 0.08) 62%
|
||||
);
|
||||
}
|
||||
|
||||
.sticky-story__copy {
|
||||
justify-content: flex-end;
|
||||
max-width: none;
|
||||
padding: calc(var(--frame-inset) + 1.35rem)
|
||||
calc(var(--frame-inset) + 1.25rem)
|
||||
calc(var(--frame-inset) + 2.1rem);
|
||||
}
|
||||
|
||||
.sticky-story__copy h2 {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.sticky-story__rail {
|
||||
top: calc(var(--frame-inset) + 1.1rem);
|
||||
bottom: auto;
|
||||
inset-inline-end: calc(var(--frame-inset) + 1.35rem);
|
||||
transform: none;
|
||||
flex-direction: row;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sticky-story {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.sticky-story__stage {
|
||||
position: relative;
|
||||
top: auto;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.sticky-story__slide,
|
||||
.sticky-story__slide.is-on,
|
||||
.sticky-story__slide.is-under {
|
||||
position: relative;
|
||||
min-height: min(78dvh, 720px);
|
||||
opacity: 1;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.sticky-story__slide.is-under .sticky-story__copy {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sticky-story__rail {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mana-canvas {
|
||||
background:
|
||||
radial-gradient(ellipse 80% 60% at 20% 20%, rgba(0, 0, 0, 0.02), transparent 55%),
|
||||
|
||||
+13
-14
@@ -1,5 +1,6 @@
|
||||
import SiteHeader from "@/components/home/SiteHeader";
|
||||
import HeroSlider from "@/components/home/HeroSlider";
|
||||
import OpeningFrame from "@/components/home/OpeningFrame";
|
||||
// import CategoryStrip from "@/components/home/CategoryStrip";
|
||||
import CategoryBento from "@/components/home/CategoryBento";
|
||||
import ProductRow from "@/components/home/ProductRow";
|
||||
@@ -8,18 +9,15 @@ import PartnerCta from "@/components/home/PartnerCta";
|
||||
import FeaturesBar from "@/components/home/FeaturesBar";
|
||||
import BlackCollection from "@/components/home/BlackCollection";
|
||||
import AboutSection from "@/components/home/AboutSection";
|
||||
import StickyStory from "@/components/home/StickyStory";
|
||||
import SiteFooter from "@/components/home/SiteFooter";
|
||||
import SubscribeModal from "@/components/home/SubscribeModal";
|
||||
import { mapHomeSlots } from "@/lib/map-home-slots";
|
||||
import {
|
||||
fetchPublicWebsiteSlots,
|
||||
itemsForSlot,
|
||||
} from "@/lib/website-slots";
|
||||
import { fetchPublicWebsiteSlots } from "@/lib/website-slots";
|
||||
import { getStorefrontCatalog } from "@/lib/products-api";
|
||||
import {
|
||||
fetchPublicCategories,
|
||||
mapCategoriesToMegaTabs,
|
||||
resolveHomeCategoryStrip,
|
||||
} from "@/lib/categories-api";
|
||||
|
||||
export default async function HomePage() {
|
||||
@@ -43,22 +41,16 @@ export default async function HomePage() {
|
||||
})),
|
||||
);
|
||||
|
||||
const categories = resolveHomeCategoryStrip(
|
||||
apiCategories,
|
||||
home.categories,
|
||||
itemsForSlot(slotItems, "category-strip").length > 0,
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<SubscribeModal />
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<HeroSlider slides={home.heroSlides} />
|
||||
</div>
|
||||
|
||||
{/* <CategoryStrip categories={categories} /> */}
|
||||
<CategoryBento categories={categories} />
|
||||
{/* Curated bento art from website slots (with local transparent defaults). */}
|
||||
<CategoryBento categories={home.categories} />
|
||||
|
||||
<div className="flex flex-col gap-10 lg:gap-14">
|
||||
<ProductRow
|
||||
@@ -84,6 +76,12 @@ export default async function HomePage() {
|
||||
stagger
|
||||
promoSlot={home.promoDiscount}
|
||||
/>
|
||||
|
||||
<OpeningFrame
|
||||
image={home.openingFrame.image}
|
||||
imageAlt={home.openingFrame.imageAlt}
|
||||
/>
|
||||
|
||||
<FeaturesBar />
|
||||
<BlackCollection items={home.blackItems} />
|
||||
<ProductRow
|
||||
@@ -103,6 +101,7 @@ export default async function HomePage() {
|
||||
image={home.about.image}
|
||||
imageSecondary={home.about.imageSecondary}
|
||||
/>
|
||||
<StickyStory steps={home.stickyStory} />
|
||||
<SiteFooter paymentBadges={home.paymentBadges} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ export default async function ProductDetailPage({ params }: PageProps) {
|
||||
.slice(0, 8);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductDetailHero
|
||||
|
||||
@@ -124,7 +124,7 @@ export default async function ProductsPage({ searchParams }: PageProps) {
|
||||
: LIST_PAGE_META.breadcrumbs;
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<main className="home-page min-h-dvh bg-background text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductListHero
|
||||
|
||||
Reference in New Issue
Block a user