Add cart shortcut above categories dock and special-cake promo image.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-12 23:57:51 +03:30
co-authored by Cursor
parent cc4334e048
commit 872d48dbc1
4 changed files with 220 additions and 33 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

+142 -4
View File
@@ -1,4 +1,4 @@
.categories-dock { .categories-dock-stack {
position: fixed; position: fixed;
bottom: 18px; bottom: 18px;
left: 0; left: 0;
@@ -6,6 +6,16 @@
z-index: 80; z-index: 80;
width: min(var(--max-container), calc(100vw - 48px)); width: min(var(--max-container), calc(100vw - 48px));
margin-inline: auto; margin-inline: auto;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
pointer-events: none;
}
.categories-dock {
position: relative;
width: 100%;
padding: 14px 24px; padding: 14px 24px;
border-radius: 28px; border-radius: 28px;
background: transparent; background: transparent;
@@ -16,7 +26,8 @@
/* Glass layer without transform — backdrop-filter breaks when combined /* Glass layer without transform — backdrop-filter breaks when combined
with transform / overflow-hidden ancestors in Chromium. */ with transform / overflow-hidden ancestors in Chromium. */
.categories-dock__glass { .categories-dock__glass,
.categories-dock-cart__glass {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 0; z-index: 0;
@@ -111,9 +122,107 @@
opacity: 0.75; opacity: 0.75;
} }
/* Cart shortcut — same glass language, shorter row above the dock */
.categories-dock-cart {
position: relative;
display: block;
width: 100%;
padding: 0;
border: none;
border-radius: 999px;
background: transparent;
cursor: pointer;
pointer-events: auto;
appearance: none;
font: inherit;
color: var(--color-primary);
transition: transform 0.2s ease;
}
.categories-dock-cart:hover {
transform: translateY(-2px);
}
.categories-dock-cart:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 3px;
}
.categories-dock-cart__inner {
position: relative;
z-index: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
padding: 10px 22px;
}
.categories-dock-cart__icon {
position: relative;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--color-primary);
color: #fff;
}
.categories-dock-cart__badge {
position: absolute;
top: -4px;
left: -4px;
min-width: 22px;
height: 22px;
padding: 0 6px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #fff;
color: var(--color-primary);
border: 1.5px solid var(--color-primary);
font-size: 12px;
font-weight: 700;
line-height: 1;
}
.categories-dock-cart__label {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
text-align: start;
}
.categories-dock-cart__name-fa {
font-size: 15px;
font-weight: 600;
line-height: 1.2;
color: var(--color-primary);
}
.categories-dock-cart__name-en {
font-family: Cailyne, serif;
font-size: 12px;
font-weight: 300;
line-height: 1.2;
color: var(--color-secondary);
opacity: 0.75;
}
@media (max-width: 900px) { @media (max-width: 900px) {
.categories-dock { .categories-dock-stack {
width: calc(100vw - 24px); width: calc(100vw - 24px);
gap: 8px;
}
.categories-dock {
padding: 12px 14px; padding: 12px 14px;
} }
@@ -131,9 +240,13 @@
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.categories-dock { .categories-dock-stack {
bottom: 10px; bottom: 10px;
width: calc(100vw - 12px); width: calc(100vw - 12px);
gap: 8px;
}
.categories-dock {
padding: 8px 8px; padding: 8px 8px;
border-radius: 22px; border-radius: 22px;
} }
@@ -163,4 +276,29 @@
.categories-dock__name-en { .categories-dock__name-en {
display: none; display: none;
} }
.categories-dock-cart {
width: 100%;
border-radius: 22px;
}
.categories-dock-cart__inner {
width: 100%;
justify-content: center;
gap: 10px;
padding: 8px 14px;
}
.categories-dock-cart__icon {
width: 40px;
height: 40px;
}
.categories-dock-cart__name-fa {
font-size: 13px;
}
.categories-dock-cart__name-en {
display: none;
}
} }
+58 -9
View File
@@ -1,5 +1,12 @@
import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { ShoppingCart } from 'lucide-react'
import {
getCartCount,
openCartDrawer,
subscribeCart,
} from '../lib/cart'
import './CategoriesDock.css' import './CategoriesDock.css'
/** /**
@@ -47,17 +54,50 @@ const DOCK_CATEGORIES = [
}, },
] as const ] as const
const glassStyle = {
backdropFilter: 'blur(20px) saturate(170%)',
WebkitBackdropFilter: 'blur(20px) saturate(170%)',
} as const
export default function CategoriesDock() { export default function CategoriesDock() {
const [cartCount, setCartCount] = useState(() => getCartCount())
useEffect(() => subscribeCart(() => setCartCount(getCartCount())), [])
return createPortal( return createPortal(
<div className="categories-dock-stack">
{cartCount > 0 && (
<button
type="button"
className="categories-dock-cart"
aria-label={`سبد خرید، ${cartCount.toLocaleString('fa-IR')} محصول`}
onClick={() => openCartDrawer()}
>
<span
className="categories-dock-cart__glass"
aria-hidden="true"
style={glassStyle}
/>
<span className="categories-dock-cart__inner">
<span className="categories-dock-cart__icon" aria-hidden="true">
<ShoppingCart size={20} strokeWidth={1.75} />
<span className="categories-dock-cart__badge">
{cartCount.toLocaleString('fa-IR')}
</span>
</span>
<span className="categories-dock-cart__label">
<span className="categories-dock-cart__name-fa">سبد خرید</span>
<span className="categories-dock-cart__name-en">Cart</span>
</span>
</span>
</button>
)}
<nav className="categories-dock" aria-label="دسته‌بندی محصولات"> <nav className="categories-dock" aria-label="دسته‌بندی محصولات">
<div <div
className="categories-dock__glass" className="categories-dock__glass"
aria-hidden="true" aria-hidden="true"
style={{ style={glassStyle}
// Inline so Vite/LightningCSS cannot drop the unprefixed property.
backdropFilter: 'blur(20px) saturate(170%)',
WebkitBackdropFilter: 'blur(20px) saturate(170%)',
}}
/> />
<ul className="categories-dock__list"> <ul className="categories-dock__list">
{DOCK_CATEGORIES.map((category) => ( {DOCK_CATEGORIES.map((category) => (
@@ -67,17 +107,26 @@ export default function CategoriesDock() {
className="categories-dock__link" className="categories-dock__link"
> >
<span className="categories-dock__image-wrap"> <span className="categories-dock__image-wrap">
<img src={category.image} alt="" className="categories-dock__image" /> <img
src={category.image}
alt=""
className="categories-dock__image"
/>
</span> </span>
<span className="categories-dock__names"> <span className="categories-dock__names">
<span className="categories-dock__name-fa">{category.nameFa}</span> <span className="categories-dock__name-fa">
<span className="categories-dock__name-en">{category.nameEn}</span> {category.nameFa}
</span>
<span className="categories-dock__name-en">
{category.nameEn}
</span>
</span> </span>
</Link> </Link>
</li> </li>
))} ))}
</ul> </ul>
</nav>, </nav>
</div>,
document.body, document.body,
) )
} }
+1 -1
View File
@@ -248,7 +248,7 @@ export default function HomePage() {
</Link> </Link>
</div> </div>
<div className="special-order-home__media"> <div className="special-order-home__media">
<img src={deliciousDecorate} alt="کیک و شیرینی سفارشی" /> <img src="/special-cake.png" alt="کیک و شیرینی سفارشی" />
</div> </div>
</div> </div>
</section> </section>