design: upgrade typography, shadows, spacing, empty states, 404 page, focus rings, noise overlay
This commit is contained in:
@@ -3,6 +3,7 @@ import { AppProviders } from '@/app/providers/AppProviders'
|
|||||||
import { AppRoutes } from '@/app/routes'
|
import { AppRoutes } from '@/app/routes'
|
||||||
import { CartSnackbar } from '@/shared/ui/CartSnackbar'
|
import { CartSnackbar } from '@/shared/ui/CartSnackbar'
|
||||||
import { ErrorBoundary } from '@/shared/ui/ErrorBoundary'
|
import { ErrorBoundary } from '@/shared/ui/ErrorBoundary'
|
||||||
|
import { NoiseOverlay } from '@/shared/ui/NoiseOverlay'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
@@ -12,6 +13,7 @@ export function App() {
|
|||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<CartSnackbar />
|
<CartSnackbar />
|
||||||
|
<NoiseOverlay />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</AppProviders>
|
</AppProviders>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
|
|
||||||
<Box component="main" sx={{ flex: 1, py: { xs: 4, md: 6 } }}>
|
<Box component="main" sx={{ flex: 1, py: { xs: 3, md: 5 } }}>
|
||||||
<Container maxWidth="lg">{children}</Container>
|
<Container maxWidth="xl" sx={{ px: { xs: 2, sm: 3, md: 4 } }}>
|
||||||
|
{children}
|
||||||
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
@@ -33,24 +35,19 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
mt: 'auto',
|
mt: 'auto',
|
||||||
borderTop: 1,
|
borderTop: 1,
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
bgcolor: 'background.default',
|
bgcolor: 'background.paper',
|
||||||
py: { xs: 4, md: 6 },
|
py: { xs: 5, md: 7 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="xl" sx={{ px: { xs: 2, sm: 3, md: 4 } }}>
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={5}>
|
||||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 700, letterSpacing: '-0.5px' }}>
|
||||||
Магазин
|
{STORE_NAME}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={1.5}>
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1, maxWidth: 260 }}>
|
||||||
<Link component={RouterLink} to="/" color="inherit" underline="hover" variant="body2">
|
|
||||||
Каталог
|
|
||||||
</Link>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
Изделия ручной работы: вещи с характером и вниманием к деталям.
|
Изделия ручной работы: вещи с характером и вниманием к деталям.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
||||||
@@ -111,7 +108,7 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider sx={{ my: 3 }} />
|
<Divider sx={{ my: 4 }} />
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'center' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'center' }}>
|
||||||
© {year} {STORE_NAME}
|
© {year} {STORE_NAME}
|
||||||
|
|||||||
@@ -92,13 +92,16 @@ function AppThemeInner({ children }: PropsWithChildren) {
|
|||||||
shape: { borderRadius: 12 },
|
shape: { borderRadius: 12 },
|
||||||
typography: {
|
typography: {
|
||||||
fontFamily: '"Outfit", "Segoe UI", system-ui, sans-serif',
|
fontFamily: '"Outfit", "Segoe UI", system-ui, sans-serif',
|
||||||
h4: { fontWeight: 700, letterSpacing: '-0.5px' },
|
h1: { fontWeight: 700, letterSpacing: '-1px', lineHeight: 1.1, textWrap: 'balance' },
|
||||||
h5: { fontWeight: 600, letterSpacing: '-0.25px' },
|
h2: { fontWeight: 700, letterSpacing: '-0.75px', lineHeight: 1.15, textWrap: 'balance' },
|
||||||
h6: { fontWeight: 600 },
|
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 },
|
subtitle1: { fontWeight: 600 },
|
||||||
subtitle2: { fontWeight: 500 },
|
subtitle2: { fontWeight: 500 },
|
||||||
body1: { fontSize: '0.875rem' },
|
body1: { fontSize: '0.875rem', lineHeight: 1.6 },
|
||||||
body2: { fontSize: '0.75rem' },
|
body2: { fontSize: '0.75rem', lineHeight: 1.5 },
|
||||||
button: { textTransform: 'none', fontWeight: 600 },
|
button: { textTransform: 'none', fontWeight: 600 },
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@@ -109,30 +112,34 @@ function AppThemeInner({ children }: PropsWithChildren) {
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
transition: 'all 0.2s ease-in-out',
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
'&:focus-visible': {
|
||||||
|
outline: '2px solid currentColor',
|
||||||
|
outlineOffset: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
contained: {
|
contained: {
|
||||||
boxShadow: '0 4px 14px 0 rgba(0,0,0,0.15)',
|
boxShadow: '0 4px 14px 0 rgba(0,0,0,0.12)',
|
||||||
'&:hover': {
|
'&: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)',
|
transform: 'translateY(-2px)',
|
||||||
},
|
},
|
||||||
'&:active': {
|
'&:active': {
|
||||||
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.15)',
|
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.12)',
|
||||||
transform: 'translateY(0)',
|
transform: 'translateY(0) scale(0.98)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
outlined: {
|
outlined: {
|
||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.1)',
|
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.08)',
|
||||||
},
|
},
|
||||||
'&:active': {
|
'&:active': {
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
|
transform: 'scale(0.98)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
boxShadow: '0 1px 3px 0 rgba(0,0,0,0.1)',
|
|
||||||
backgroundColor: 'action.hover',
|
backgroundColor: 'action.hover',
|
||||||
},
|
},
|
||||||
'&:active': {
|
'&:active': {
|
||||||
@@ -147,12 +154,48 @@ function AppThemeInner({ children }: PropsWithChildren) {
|
|||||||
transition: 'all 0.2s ease-in-out',
|
transition: 'all 0.2s ease-in-out',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'action.hover',
|
backgroundColor: 'action.hover',
|
||||||
transform: 'scale(1.1)',
|
transform: 'scale(1.08)',
|
||||||
},
|
},
|
||||||
'&:active': {
|
'&:active': {
|
||||||
backgroundColor: 'action.selected',
|
backgroundColor: 'action.selected',
|
||||||
transform: 'scale(0.95)',
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
@@ -37,4 +40,6 @@ body,
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ const ProductCardInner = ({ product, mediaHeight = 200, actions }: Props) => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
borderRadius: 3,
|
borderRadius: '16px 16px 12px 12px',
|
||||||
border: '1px solid',
|
border: 'none',
|
||||||
borderColor: 'divider',
|
|
||||||
bgcolor: 'background.paper',
|
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': {
|
'&:hover': {
|
||||||
transform: 'translateY(-4px)',
|
transform: 'translateY(-6px)',
|
||||||
boxShadow: '0 8px 30px rgba(0,0,0,0.10)',
|
boxShadow: '0 12px 40px rgba(0,0,0,0.12)',
|
||||||
},
|
},
|
||||||
'&:hover .product-card__media': { transform: 'scale(1.06)' },
|
'&:hover .product-card__media': { transform: 'scale(1.06)' },
|
||||||
'&:hover .product-card__title': { color: 'primary.main' },
|
'&:hover .product-card__title': { color: 'primary.main' },
|
||||||
@@ -236,7 +236,11 @@ const ProductCardInner = ({ product, mediaHeight = 200, actions }: Props) => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pt: 1.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pt: 1.5 }}>
|
||||||
<Typography variant="h6" color="primary" sx={{ fontWeight: 700, fontSize: '1.1rem' }}>
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
color="primary"
|
||||||
|
sx={{ fontWeight: 700, fontSize: '1.1rem', fontVariantNumeric: 'tabular-nums' }}
|
||||||
|
>
|
||||||
{formatPriceRub(product.priceCents)}
|
{formatPriceRub(product.priceCents)}
|
||||||
</Typography>
|
</Typography>
|
||||||
{actions}
|
{actions}
|
||||||
|
|||||||
@@ -59,7 +59,19 @@ export function CartPage() {
|
|||||||
|
|
||||||
{cartQuery.isError && <Alert severity="error">Не удалось загрузить корзину.</Alert>}
|
{cartQuery.isError && <Alert severity="error">Не удалось загрузить корзину.</Alert>}
|
||||||
|
|
||||||
{cartQuery.isSuccess && items.length === 0 && <Alert severity="info">Корзина пуста.</Alert>}
|
{cartQuery.isSuccess && items.length === 0 && (
|
||||||
|
<Box sx={{ textAlign: 'center', py: 8 }}>
|
||||||
|
<Typography variant="h6" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
Корзина пуста
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
|
Добавьте что-нибудь из каталога, чтобы оформить заказ.
|
||||||
|
</Typography>
|
||||||
|
<Button component={RouterLink} to="/" variant="contained">
|
||||||
|
Перейти в каталог
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
{items.length > 0 && (
|
{items.length > 0 && (
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ export function HomePage() {
|
|||||||
<Box>
|
<Box>
|
||||||
<CatalogSlider />
|
<CatalogSlider />
|
||||||
|
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Typography variant="h4" component="h1" sx={{ mb: 1 }}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 3, maxWidth: 560 }}>
|
||||||
Игрушки, сувениры и другие изделия ручной работы.
|
Игрушки, сувениры и другие изделия ручной работы.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -102,9 +102,14 @@ export function HomePage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{productsQuery.isSuccess && products.length === 0 && (
|
{productsQuery.isSuccess && products.length === 0 && (
|
||||||
<Typography color="text.secondary" sx={{ mt: 2 }}>
|
<Box sx={{ textAlign: 'center', py: 8 }}>
|
||||||
Пока нет опубликованных товаров.
|
<Typography variant="h6" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
Пока нет опубликованных товаров
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Загляните позже — мы регулярно обновляем каталог.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{productsQuery.isSuccess && products.length > 0 && (
|
{productsQuery.isSuccess && products.length > 0 && (
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { Box, Typography, Button, Stack, Paper } from '@mui/material'
|
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() {
|
export function NotFoundPage() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
minHeight: '100vh',
|
minHeight: '100dvh',
|
||||||
bgcolor: 'background.default',
|
bgcolor: 'background.default',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -15,28 +17,44 @@ export function NotFoundPage() {
|
|||||||
px: 2,
|
px: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paper sx={{ p: 4, borderRadius: 3, bgcolor: 'background.paper' }}>
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
p: { xs: 4, md: 6 },
|
||||||
|
borderRadius: 4,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
maxWidth: 480,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Stack spacing={3} sx={{ alignItems: 'center', textAlign: 'center' }}>
|
<Stack spacing={3} sx={{ alignItems: 'center', textAlign: 'center' }}>
|
||||||
<Box sx={{ fontSize: 96, lineHeight: 1 }}>404</Box>
|
<Typography
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
variant="h1"
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: 72, md: 96 },
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: 1,
|
||||||
|
color: 'primary.main',
|
||||||
|
opacity: 0.15,
|
||||||
|
letterSpacing: '-4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
404
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h5" component="h1" sx={{ fontWeight: 700, mt: -1 }}>
|
||||||
Страница не найдена
|
Страница не найдена
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 400 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 360 }}>
|
||||||
Извините, но запрашиваемая страница не существует или была удалена.
|
Извините, но запрашиваемая страница не существует или была удалена.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" spacing={2} sx={{ mt: 2 }}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mt: 1, width: '100%' }}>
|
||||||
<Button
|
<Button variant="contained" size="large" onClick={() => navigate('/')} sx={{ flexGrow: 1, px: 4 }}>
|
||||||
variant="contained"
|
На главную
|
||||||
size="large"
|
|
||||||
onClick={() => {
|
|
||||||
window.location.href = '/'
|
|
||||||
}}
|
|
||||||
sx={{ px: 4 }}
|
|
||||||
>
|
|
||||||
Вернуться на главную
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" size="large" component={RouterLink} to="/" sx={{ px: 4 }}>
|
<Button variant="outlined" size="large" component={RouterLink} to="/" sx={{ flexGrow: 1, px: 4 }}>
|
||||||
Посмотреть каталог
|
Каталог
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -72,14 +72,14 @@ export function ProductPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
{imageUrls.length > 0 ? (
|
{imageUrls.length > 0 ? (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: 2,
|
borderRadius: '20px 20px 12px 12px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
border: 1,
|
border: 'none',
|
||||||
borderColor: 'divider',
|
boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -150,10 +150,10 @@ export function ProductPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Typography variant="h4" component="h1">
|
<Typography variant="h3" component="h1" sx={{ fontWeight: 700, letterSpacing: '-0.75px' }}>
|
||||||
{p.title}
|
{p.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" color="primary">
|
<Typography variant="h4" color="primary" sx={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>
|
||||||
{formatPriceRub(p.priceCents)}
|
{formatPriceRub(p.priceCents)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import type { SxProps, Theme } from '@mui/material/styles'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
opacity?: number
|
||||||
|
sx?: SxProps<Theme>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NoiseOverlay({ opacity = 0.03, sx }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: 9999,
|
||||||
|
opacity,
|
||||||
|
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
|
||||||
|
backgroundRepeat: 'repeat',
|
||||||
|
backgroundSize: '256px 256px',
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user