diff --git a/client/src/app/App.tsx b/client/src/app/App.tsx
index 76a90d8..c6feb8e 100644
--- a/client/src/app/App.tsx
+++ b/client/src/app/App.tsx
@@ -3,6 +3,7 @@ import { AppProviders } from '@/app/providers/AppProviders'
import { AppRoutes } from '@/app/routes'
import { CartSnackbar } from '@/shared/ui/CartSnackbar'
import { ErrorBoundary } from '@/shared/ui/ErrorBoundary'
+import { NoiseOverlay } from '@/shared/ui/NoiseOverlay'
export function App() {
return (
@@ -12,6 +13,7 @@ export function App() {
+
)
diff --git a/client/src/app/layout/MainLayout.tsx b/client/src/app/layout/MainLayout.tsx
index fe683e7..e67c5d7 100644
--- a/client/src/app/layout/MainLayout.tsx
+++ b/client/src/app/layout/MainLayout.tsx
@@ -23,8 +23,10 @@ export function MainLayout({ children }: PropsWithChildren) {
-
- {children}
+
+
+ {children}
+
-
-
+
+
-
- Магазин
+
+ {STORE_NAME}
+
+
+ Изделия ручной работы: вещи с характером и вниманием к деталям.
-
-
- Каталог
-
-
- Изделия ручной работы: вещи с характером и вниманием к деталям.
-
-
@@ -111,7 +108,7 @@ export function MainLayout({ children }: PropsWithChildren) {
-
+
© {year} {STORE_NAME}
diff --git a/client/src/app/providers/AppProviders.tsx b/client/src/app/providers/AppProviders.tsx
index 2e26cf5..0f15afb 100644
--- a/client/src/app/providers/AppProviders.tsx
+++ b/client/src/app/providers/AppProviders.tsx
@@ -92,13 +92,16 @@ function AppThemeInner({ children }: PropsWithChildren) {
shape: { borderRadius: 12 },
typography: {
fontFamily: '"Outfit", "Segoe UI", system-ui, sans-serif',
- h4: { fontWeight: 700, letterSpacing: '-0.5px' },
- h5: { fontWeight: 600, letterSpacing: '-0.25px' },
- h6: { fontWeight: 600 },
+ h1: { fontWeight: 700, letterSpacing: '-1px', lineHeight: 1.1, textWrap: 'balance' },
+ h2: { fontWeight: 700, letterSpacing: '-0.75px', lineHeight: 1.15, textWrap: 'balance' },
+ h3: { fontWeight: 700, letterSpacing: '-0.5px', lineHeight: 1.2, textWrap: 'balance' },
+ h4: { fontWeight: 700, letterSpacing: '-0.5px', textWrap: 'balance' },
+ h5: { fontWeight: 600, letterSpacing: '-0.25px', textWrap: 'balance' },
+ h6: { fontWeight: 600, textWrap: 'balance' },
subtitle1: { fontWeight: 600 },
subtitle2: { fontWeight: 500 },
- body1: { fontSize: '0.875rem' },
- body2: { fontSize: '0.75rem' },
+ body1: { fontSize: '0.875rem', lineHeight: 1.6 },
+ body2: { fontSize: '0.75rem', lineHeight: 1.5 },
button: { textTransform: 'none', fontWeight: 600 },
},
components: {
@@ -109,30 +112,34 @@ function AppThemeInner({ children }: PropsWithChildren) {
borderRadius: 12,
fontWeight: 600,
transition: 'all 0.2s ease-in-out',
+ '&:focus-visible': {
+ outline: '2px solid currentColor',
+ outlineOffset: 2,
+ },
},
contained: {
- boxShadow: '0 4px 14px 0 rgba(0,0,0,0.15)',
+ boxShadow: '0 4px 14px 0 rgba(0,0,0,0.12)',
'&:hover': {
- boxShadow: '0 6px 20px 0 rgba(0,0,0,0.25)',
+ boxShadow: '0 6px 20px 0 rgba(0,0,0,0.18)',
transform: 'translateY(-2px)',
},
'&:active': {
- boxShadow: '0 2px 8px 0 rgba(0,0,0,0.15)',
- transform: 'translateY(0)',
+ boxShadow: '0 2px 8px 0 rgba(0,0,0,0.12)',
+ transform: 'translateY(0) scale(0.98)',
},
},
outlined: {
border: '1px solid',
'&:hover': {
- boxShadow: '0 2px 8px 0 rgba(0,0,0,0.1)',
+ boxShadow: '0 2px 8px 0 rgba(0,0,0,0.08)',
},
'&:active': {
boxShadow: 'none',
+ transform: 'scale(0.98)',
},
},
text: {
'&:hover': {
- boxShadow: '0 1px 3px 0 rgba(0,0,0,0.1)',
backgroundColor: 'action.hover',
},
'&:active': {
@@ -147,12 +154,48 @@ function AppThemeInner({ children }: PropsWithChildren) {
transition: 'all 0.2s ease-in-out',
'&:hover': {
backgroundColor: 'action.hover',
- transform: 'scale(1.1)',
+ transform: 'scale(1.08)',
},
'&:active': {
backgroundColor: 'action.selected',
transform: 'scale(0.95)',
},
+ '&:focus-visible': {
+ outline: '2px solid currentColor',
+ outlineOffset: 2,
+ },
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ '&:focus-visible': {
+ outline: '2px solid currentColor',
+ outlineOffset: 2,
+ },
+ },
+ },
+ },
+ MuiLink: {
+ styleOverrides: {
+ root: {
+ '&:focus-visible': {
+ outline: '2px solid currentColor',
+ outlineOffset: 2,
+ borderRadius: 2,
+ },
+ },
+ },
+ },
+ MuiInputBase: {
+ styleOverrides: {
+ root: {
+ '&.Mui-focused': {
+ '& .MuiOutlinedInput-notchedOutline': {
+ borderWidth: 2,
+ },
+ },
},
},
},
diff --git a/client/src/app/styles/global.css b/client/src/app/styles/global.css
index 0962570..fa3723d 100644
--- a/client/src/app/styles/global.css
+++ b/client/src/app/styles/global.css
@@ -30,6 +30,9 @@
:root {
color-scheme: light;
}
+html {
+ scroll-behavior: smooth;
+}
html,
body,
#root {
@@ -37,4 +40,6 @@ body,
}
body {
margin: 0;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
diff --git a/client/src/entities/product/ui/ProductCard.tsx b/client/src/entities/product/ui/ProductCard.tsx
index bccadae..210eaa8 100644
--- a/client/src/entities/product/ui/ProductCard.tsx
+++ b/client/src/entities/product/ui/ProductCard.tsx
@@ -58,14 +58,14 @@ const ProductCardInner = ({ product, mediaHeight = 200, actions }: Props) => {
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
- borderRadius: 3,
- border: '1px solid',
- borderColor: 'divider',
+ borderRadius: '16px 16px 12px 12px',
+ border: 'none',
bgcolor: 'background.paper',
- transition: 'transform 200ms ease, box-shadow 250ms ease',
+ boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
+ transition: 'transform 250ms ease, box-shadow 300ms ease',
'&:hover': {
- transform: 'translateY(-4px)',
- boxShadow: '0 8px 30px rgba(0,0,0,0.10)',
+ transform: 'translateY(-6px)',
+ boxShadow: '0 12px 40px rgba(0,0,0,0.12)',
},
'&:hover .product-card__media': { transform: 'scale(1.06)' },
'&:hover .product-card__title': { color: 'primary.main' },
@@ -236,7 +236,11 @@ const ProductCardInner = ({ product, mediaHeight = 200, actions }: Props) => {
-
+
{formatPriceRub(product.priceCents)}
{actions}
diff --git a/client/src/pages/cart/ui/CartPage.tsx b/client/src/pages/cart/ui/CartPage.tsx
index fe4ece2..5f8ff40 100644
--- a/client/src/pages/cart/ui/CartPage.tsx
+++ b/client/src/pages/cart/ui/CartPage.tsx
@@ -59,7 +59,19 @@ export function CartPage() {
{cartQuery.isError && Не удалось загрузить корзину.}
- {cartQuery.isSuccess && items.length === 0 && Корзина пуста.}
+ {cartQuery.isSuccess && items.length === 0 && (
+
+
+ Корзина пуста
+
+
+ Добавьте что-нибудь из каталога, чтобы оформить заказ.
+
+
+
+ )}
{items.length > 0 && (
diff --git a/client/src/pages/home/ui/HomePage.tsx b/client/src/pages/home/ui/HomePage.tsx
index df542bf..c7640d3 100644
--- a/client/src/pages/home/ui/HomePage.tsx
+++ b/client/src/pages/home/ui/HomePage.tsx
@@ -70,10 +70,10 @@ export function HomePage() {
-
+
{title}
-
+
Игрушки, сувениры и другие изделия ручной работы.
@@ -102,9 +102,14 @@ export function HomePage() {
)}
{productsQuery.isSuccess && products.length === 0 && (
-
- Пока нет опубликованных товаров.
-
+
+
+ Пока нет опубликованных товаров
+
+
+ Загляните позже — мы регулярно обновляем каталог.
+
+
)}
{productsQuery.isSuccess && products.length > 0 && (
diff --git a/client/src/pages/not-found/ui/NotFoundPage.tsx b/client/src/pages/not-found/ui/NotFoundPage.tsx
index 2ac2831..2d25d2c 100644
--- a/client/src/pages/not-found/ui/NotFoundPage.tsx
+++ b/client/src/pages/not-found/ui/NotFoundPage.tsx
@@ -1,13 +1,15 @@
import { Box, Typography, Button, Stack, Paper } from '@mui/material'
-import { Link as RouterLink } from 'react-router-dom'
+import { Link as RouterLink, useNavigate } from 'react-router-dom'
export function NotFoundPage() {
+ const navigate = useNavigate()
+
return (
-
+
- 404
-
+
+ 404
+
+
Страница не найдена
-
+
Извините, но запрашиваемая страница не существует или была удалена.
-
-
diff --git a/client/src/pages/product/ui/ProductPage.tsx b/client/src/pages/product/ui/ProductPage.tsx
index 3d39f2f..9e71952 100644
--- a/client/src/pages/product/ui/ProductPage.tsx
+++ b/client/src/pages/product/ui/ProductPage.tsx
@@ -72,14 +72,14 @@ export function ProductPage() {
return (
-
+
{imageUrls.length > 0 ? (
@@ -150,10 +150,10 @@ export function ProductPage() {
)}
-
+
{p.title}
-
+
{formatPriceRub(p.priceCents)}
diff --git a/client/src/shared/ui/NoiseOverlay.tsx b/client/src/shared/ui/NoiseOverlay.tsx
new file mode 100644
index 0000000..b057d6f
--- /dev/null
+++ b/client/src/shared/ui/NoiseOverlay.tsx
@@ -0,0 +1,25 @@
+import Box from '@mui/material/Box'
+import type { SxProps, Theme } from '@mui/material/styles'
+
+type Props = {
+ opacity?: number
+ sx?: SxProps
+}
+
+export function NoiseOverlay({ opacity = 0.03, sx }: Props) {
+ return (
+
+ )
+}
diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db
index 545a3e2..bc5607c 100644
Binary files a/server/prisma/prisma/dev.db and b/server/prisma/prisma/dev.db differ