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