init commit,

This commit is contained in:
louiscklaw
2025-05-28 09:55:51 +08:00
commit efe70ceb69
8042 changed files with 951668 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
import type { BoxProps } from '@mui/material/Box';
import { m } from 'framer-motion';
import { varAlpha } from 'minimal-shared/utils';
import Box from '@mui/material/Box';
import useMediaQuery from '@mui/material/useMediaQuery';
import { CONFIG } from 'src/global-config';
import { MotionContainer } from 'src/components/animate';
import { Dots, Lines, Texts, Circles, PlusIcon } from './hero-svg';
// ----------------------------------------------------------------------
export function HeroBackground({ sx, ...other }: BoxProps) {
const mdUp = useMediaQuery((theme) => theme.breakpoints.up('md'));
const strokeCount = 12;
return (
<MotionContainer>
<Box
sx={[
(theme) => ({
'--stroke-dasharray': 3,
'--stroke-spacing': '80px',
/* line */
'--hero-line-stroke-width': 1,
'--hero-line-stroke-color': varAlpha(theme.vars.palette.grey['500Channel'], 0.32),
...theme.applyStyles('dark', {
'--hero-line-stroke-color': varAlpha(theme.vars.palette.grey['600Channel'], 0.16),
}),
/* text */
'--hero-text-stroke-width': 1,
'--hero-text-stroke-color': varAlpha(theme.vars.palette.grey['500Channel'], 0.24),
...theme.applyStyles('dark', {
'--hero-text-stroke-color': varAlpha(theme.vars.palette.grey['600Channel'], 0.12),
}),
/* circle */
'--hero-circle-stroke-width': 1,
'--hero-circle-stroke-color': varAlpha(theme.vars.palette.grey['500Channel'], 0.48),
...theme.applyStyles('dark', {
'--hero-circle-stroke-color': varAlpha(theme.vars.palette.grey['600Channel'], 0.24),
}),
/* plus */
'--hero-plus-stroke-color': theme.vars.palette.text.disabled,
top: 0,
left: 0,
width: 1,
height: 1,
position: 'absolute',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<Dots />
{mdUp && <Texts />}
<Box
component={m.svg}
xmlns="http://www.w3.org/2000/svg"
width="1440"
height="1080"
fill="none"
viewBox="0 0 1440 1080"
initial="hidden"
animate="visible"
sx={[{ width: 1, height: 1 }]}
>
<defs>
<radialGradient
id="mask_gradient_id"
cx="0"
cy="0"
r="1"
gradientTransform="matrix(720 0 0 420 720 560)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" stopColor="#FFFFFF" stopOpacity={1} />
<stop offset="100%" stopColor="#FFFFFF" stopOpacity={0.08} />
</radialGradient>
<mask id="mask_id">
<ellipse cx="50%" cy="50%" rx="50%" ry="36%" fill="url(#mask_gradient_id)" />
</mask>
</defs>
<g mask="url(#mask_id)">
<Circles />
<PlusIcon />
<Lines strokeCount={strokeCount} />
</g>
</Box>
<Box
component={m.div}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
sx={[
(theme) => ({
...theme.mixins.bgGradient({
images: [
`linear-gradient(180deg, ${theme.vars.palette.background.default} 12%, ${varAlpha(theme.vars.palette.background.defaultChannel, 0.92)} 50%, ${theme.vars.palette.background.default} 88%)`,
`url(${CONFIG.assetsDir}/assets/background/background-3.webp)`,
],
}),
top: 0,
left: 0,
width: 1,
height: 1,
zIndex: -1,
position: 'absolute',
...theme.applyStyles('dark', {
...theme.mixins.bgGradient({
images: [
`url(${CONFIG.assetsDir}/assets/images/home/hero-blur.webp)`,
`linear-gradient(180deg, ${theme.vars.palette.background.default} 12%, ${varAlpha(theme.vars.palette.background.defaultChannel, 0.96)} 50%, ${theme.vars.palette.background.default} 88%)`,
`url(${CONFIG.assetsDir}/assets/background/background-3.webp)`,
],
}),
}),
}),
]}
/>
</Box>
</MotionContainer>
);
}

View File

@@ -0,0 +1,332 @@
import type { MotionProps } from 'framer-motion';
import type { BoxProps } from '@mui/material/Box';
import type { PaletteColorKey } from 'src/theme/core';
import type { Theme, SxProps } from '@mui/material/styles';
import { m } from 'framer-motion';
import Box from '@mui/material/Box';
import { varFade } from 'src/components/animate';
// ----------------------------------------------------------------------
export function Lines({ strokeCount }: { strokeCount: number }) {
const drawVariants = {
x: {
hidden: { x2: 0, strokeOpacity: 0 },
visible: (i: number) => {
const delay = 1 + i * 0.5;
return {
x2: '100%',
strokeOpacity: 1,
transition: {
strokeOpacity: { delay, duration: 0.01 },
x2: { delay, bounce: 0, duration: 1.5, type: 'spring' },
},
};
},
},
y: {
hidden: { y2: 0, strokeOpacity: 0 },
visible: (i: number) => {
const delay = 1 + i * 0.5;
return {
y2: '100%',
strokeOpacity: 1,
transition: {
strokeOpacity: { delay, duration: 0.01 },
y2: { delay, bounce: 0, duration: 1.5, type: 'spring' },
},
};
},
},
};
const translateY = (index: number) =>
strokeCount / 2 > index
? `translateY(calc(((${index} * var(--stroke-spacing)) + var(--stroke-spacing) / 2) * -1))`
: `translateY(calc(((${strokeCount - (index + 1)} * var(--stroke-spacing)) + var(--stroke-spacing) / 2)))`;
const linesX = (
<>
{Array.from({ length: strokeCount }, (_, index) => (
<m.line
key={index}
x1="0"
x2="100%"
y1="50%"
y2="50%"
variants={drawVariants.x}
style={{
transform: translateY(index),
stroke: 'var(--hero-line-stroke-color)',
strokeDasharray: 'var(--stroke-dasharray)',
strokeWidth: 'var(--hero-line-stroke-width)',
}}
/>
))}
</>
);
const translateX = (index: number) =>
strokeCount / 2 > index
? `translateX(calc(((${index} * var(--stroke-spacing)) + var(--stroke-spacing) / 2) * -1))`
: `translateX(calc(((${strokeCount - (index + 1)} * var(--stroke-spacing)) + var(--stroke-spacing) / 2)))`;
const linesY = (
<>
{Array.from({ length: strokeCount }, (_, index) => (
<m.line
key={index}
x1="50%"
x2="50%"
y1="0%"
y2="100%"
variants={drawVariants.y}
style={{
transform: translateX(index),
stroke: 'var(--hero-line-stroke-color)',
strokeDasharray: 'var(--stroke-dasharray)',
strokeWidth: 'var(--hero-line-stroke-width)',
}}
/>
))}
</>
);
return (
<>
{linesX}
{linesY}
</>
);
}
// ----------------------------------------------------------------------
export function Circles() {
const drawCircle = {
hidden: { opacity: 0 },
visible: (i: number) => {
const delay = 1 + i * 0.5;
return { opacity: 1, transition: { opacity: { delay, duration: 0.01 } } };
},
};
return (
<>
<m.path
variants={drawCircle}
d="M1 41C1 63.0914 18.9086 81 41 81C63.0914 81 81 63.0914 81 41C81 18.9086 63.0914 1 41 1"
style={{
strokeDasharray: 'var(--stroke-dasharray)',
stroke: 'var(--hero-circle-stroke-color)',
strokeWidth: 'var(--hero-circle-stroke-width)',
transform: 'translate(calc(50% - 480px), calc(50% - 80px))',
}}
/>
<m.path
variants={drawCircle}
d="M1 41C1 63.0914 18.9086 81 41 81C63.0914 81 81 63.0914 81 41C81 18.9086 63.0914 1 41 1"
style={{
strokeDasharray: 'var(--stroke-dasharray)',
stroke: 'var(--hero-circle-stroke-color)',
strokeWidth: 'var(--hero-circle-stroke-width)',
transform: 'translate(calc(50% + 400px), calc(50% + 80px))',
}}
/>
<m.circle
cx="50%"
cy="50%"
fill="var(--hero-circle-stroke-color)"
style={{ transform: 'translate(calc(0% - 200px), calc(0% + 200px))' }}
initial={{ r: 0 }}
animate={{ r: 5 }}
/>
</>
);
}
// ----------------------------------------------------------------------
export function PlusIcon() {
const drawPlus = {
hidden: { opacity: 0, pathLength: 0 },
visible: (i: number) => {
const delay = 1 + i * 0.5;
return {
opacity: 1,
pathLength: 1,
transition: {
opacity: { delay, duration: 0.01 },
pathLength: { delay, bounce: 0, duration: 1.5, type: 'spring' },
},
};
},
};
return (
<>
<m.path
variants={drawPlus}
d="M8 0V16M16 8.08889H0"
stroke="var(--hero-plus-stroke-color)"
style={{ transform: 'translate(calc(50% - 448px), calc(50% - 128px))' }}
/>
<m.path
variants={drawPlus}
d="M8 0V16M16 8.08889H0"
stroke="var(--hero-plus-stroke-color)"
style={{ transform: 'translate(calc(50% + 432px), calc(50% + 192px))' }}
/>
</>
);
}
// ----------------------------------------------------------------------
export function Texts({ sx, ...other }: BoxProps & MotionProps) {
return (
<Box
component={m.div}
variants={varFade('in')}
sx={[
() => ({
left: 0,
width: 1,
bottom: 0,
height: 200,
position: 'absolute',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<Box
component="svg"
sx={[
(theme) => ({
width: 1,
height: 1,
'& text': {
fill: 'none',
fontSize: 200,
fontWeight: 800,
strokeDasharray: 4,
textTransform: 'uppercase',
stroke: 'var(--hero-text-stroke-color)',
strokeWidth: 'var(--hero-text-stroke-width)',
fontFamily: theme.typography.fontSecondaryFamily,
},
}),
]}
>
<m.text
x="0"
y="12px"
dominantBaseline="hanging"
animate={{ x: ['0%', '-50%'] }}
transition={{ duration: 64, ease: 'linear', repeat: Infinity }}
>
Minimal Design System Minimal Design System
</m.text>
</Box>
</Box>
);
}
// ----------------------------------------------------------------------
type DotProps = Pick<MotionProps, 'animate' | 'transition'> & {
sx?: SxProps<Theme>;
color?: PaletteColorKey;
};
function Dot({ color = 'primary', animate, transition, sx, ...other }: DotProps) {
return (
<Box
component={m.div}
variants={{
initial: { opacity: 0 },
animate: { opacity: 1, transition: { duration: 0.64, ease: [0.43, 0.13, 0.23, 0.96] } },
}}
sx={[
() => ({
width: 12,
height: 12,
top: '50%',
left: '50%',
position: 'absolute',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<Box
component={m.div}
animate={animate}
transition={
transition ?? {
duration: 6,
ease: 'linear',
repeat: Infinity,
repeatType: 'reverse',
}
}
sx={[
(theme) => ({
width: 1,
height: 1,
borderRadius: '50%',
boxShadow: `0px -2px 4px 0px ${theme.vars.palette[color].main} inset`,
background: `linear-gradient(135deg, ${theme.vars.palette[color].lighter}, ${theme.vars.palette[color].light})`,
...theme.applyStyles('dark', {
boxShadow: `0px -2px 4px 0px ${theme.vars.palette[color].dark} inset`,
}),
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
/>
</Box>
);
}
export function Dots() {
return (
<>
<Dot
color="error"
animate={{ x: [0, 24] }}
sx={{ width: 14, height: 14, transform: 'translate(calc(50% - 457px), calc(50% - 259px))' }}
/>
<Dot
color="warning"
animate={{ y: [0, 24] }}
sx={{ transform: 'translate(calc(50% - 356px), calc(50% + 37px))' }}
/>
<Dot
color="info"
animate={{ x: [0, 24] }}
sx={{ transform: 'translate(calc(50% + 332px), calc(50% + 135px))' }}
/>
<Dot
color="secondary"
animate={{ x: [0, 24] }}
sx={{ transform: 'translate(calc(50% + 430px), calc(50% - 160px))' }}
/>
<Dot
color="success"
animate={{ y: [0, 24] }}
sx={{ transform: 'translate(calc(50% + 136px), calc(50% + 332px))' }}
/>
</>
);
}

View File

@@ -0,0 +1,117 @@
import type { MotionProps } from 'framer-motion';
import type { BoxProps } from '@mui/material/Box';
import type { Theme, SxProps } from '@mui/material/styles';
import { m } from 'framer-motion';
import { varAlpha } from 'minimal-shared/utils';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { varFade } from 'src/components/animate';
// ----------------------------------------------------------------------
type TextProps = {
sx?: SxProps<Theme>;
title: React.ReactNode;
variants?: MotionProps['variants'];
};
type SectionTitleProps = BoxProps & {
txtGradient?: string;
title: React.ReactNode;
caption?: React.ReactNode;
description?: React.ReactNode;
slotProps?: {
title?: Omit<TextProps, 'title'>;
caption?: Omit<TextProps, 'title'>;
description?: Omit<TextProps, 'title'>;
};
};
export function SectionTitle({
sx,
title,
caption,
slotProps,
txtGradient,
description,
...other
}: SectionTitleProps) {
return (
<Box
sx={[
{
gap: 3,
display: 'flex',
flexDirection: 'column',
},
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
{caption && (
<SectionCaption
title={caption}
variants={slotProps?.caption?.variants}
sx={slotProps?.caption?.sx}
/>
)}
<Typography
component={m.h2}
variant="h2"
variants={slotProps?.title?.variants ?? varFade('inUp', { distance: 24 })}
sx={slotProps?.title?.sx}
>
{`${title} `}
<Box
component="span"
sx={(theme) => ({
opacity: 0.4,
display: 'inline-block',
...theme.mixins.textGradient(
`to right, ${theme.vars.palette.text.primary}, ${varAlpha(theme.vars.palette.text.primaryChannel, 0.2)}`
),
})}
>
{txtGradient}
</Box>
</Typography>
{description && (
<Typography
component={m.p}
variants={slotProps?.description?.variants ?? varFade('inUp', { distance: 24 })}
sx={[
{ color: 'text.secondary' },
...(Array.isArray(slotProps?.description?.sx)
? (slotProps?.description?.sx ?? [])
: [slotProps?.description?.sx]),
]}
>
{description}
</Typography>
)}
</Box>
);
}
// ----------------------------------------------------------------------
export function SectionCaption({ title, variants, sx, ...other }: TextProps) {
return (
<Box
component={m.span}
variants={variants ?? varFade('inUp', { distance: 24 })}
sx={[
() => ({ typography: 'overline', color: 'text.disabled' }),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
{title}
</Box>
);
}

View File

@@ -0,0 +1,287 @@
import type { BoxProps } from '@mui/material/Box';
import type { Theme, SxProps } from '@mui/material/styles';
import type { Transition, MotionProps } from 'framer-motion';
import { useId } from 'react';
import { m } from 'framer-motion';
import Box from '@mui/material/Box';
import { styled } from '@mui/material/styles';
import { varFade } from 'src/components/animate';
// ----------------------------------------------------------------------
const baseStyles = (theme: Theme): SxProps<Theme> => ({
zIndex: 2,
display: 'none',
color: 'grey.500',
position: 'absolute',
'& line': { strokeDasharray: 3, stroke: 'currentColor' },
'& path': { fill: 'currentColor', stroke: 'currentColor' },
[theme.breakpoints.up(1440)]: { display: 'block' },
});
const transition: Transition = {
duration: 0.64,
ease: [0.43, 0.13, 0.23, 0.96],
};
type SvgRootProps = React.ComponentProps<typeof SvgRoot>;
const SvgRoot = styled(m.svg)``;
// ----------------------------------------------------------------------
export function FloatLine({ sx, vertical, ...other }: SvgRootProps & { vertical?: boolean }) {
return (
<SvgRoot
sx={[
(theme) => ({
...baseStyles(theme),
width: 1,
zIndex: 1,
height: '1px',
opacity: 0.24,
...(vertical && { width: '1px', height: 1 }),
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
{vertical ? (
<m.line
x1="0.5"
x2="0.5"
y1="0"
y2="100%"
variants={{
initial: { y2: '0%' },
animate: { y2: '100%', transition },
}}
/>
) : (
<m.line
x1="0"
x2="100%"
y1="0.5"
y2="0.5"
variants={{
initial: { x2: '0%' },
animate: { x2: '100%', transition },
}}
/>
)}
</SvgRoot>
);
}
// ----------------------------------------------------------------------
export function FloatPlusIcon({ sx, ...other }: SvgRootProps) {
return (
<SvgRoot
variants={{
initial: { scale: 0 },
animate: { scale: 1, transition },
}}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
sx={[
(theme) => ({
...baseStyles(theme),
width: 16,
height: 16,
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<path d="M8 0V16M16 8.08889H0" />
</SvgRoot>
);
}
// ----------------------------------------------------------------------
export function FloatXIcon({ sx, ...other }: SvgRootProps) {
return (
<SvgRoot
variants={{
initial: { scaleX: 0 },
animate: { scaleX: 1, transition },
}}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
sx={[
(theme) => ({
...baseStyles(theme),
width: 16,
height: 16,
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<path d="M14 2L7.96685 8.03315M7.96685 8.03315L2.0663 13.9337M7.96685 8.03315L13.9337 14M7.96685 8.03315L2 2.0663" />
</SvgRoot>
);
}
// ----------------------------------------------------------------------
export function FloatTriangleLeftIcon({ sx, ...other }: SvgRootProps) {
return (
<SvgRoot
variants={{
initial: { scaleY: 0 },
animate: { scaleY: 1, transition },
}}
width="10"
height="20"
viewBox="0 0 10 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
sx={[
(theme) => ({
...baseStyles(theme),
width: 10,
height: 20,
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<path d="M10 10L8.74228e-07 20L0 0L10 10Z" />
</SvgRoot>
);
}
export function FloatTriangleDownIcon({ sx, ...other }: SvgRootProps) {
return (
<SvgRoot
variants={{
initial: { scaleX: 0 },
animate: { scaleX: 1, transition },
}}
width="20"
height="10"
viewBox="0 0 20 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
sx={[
(theme) => ({
...baseStyles(theme),
width: 20,
height: 10,
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
>
<path d="M10 10L0 0H20L10 10Z" />
</SvgRoot>
);
}
// ----------------------------------------------------------------------
export function CircleSvg({ sx, variants }: SvgRootProps) {
const maskId = useId();
const clipPathId = useId();
const gradientId = useId();
return (
<SvgRoot
width="100%"
height="100%"
viewBox="0 0 560 560"
xmlns="http://www.w3.org/2000/svg"
fill="none"
variants={variants ?? varFade('in')}
sx={[
() => ({
top: 0,
left: 0,
right: 0,
bottom: 0,
m: 'auto',
width: 560,
height: 560,
color: 'grey.500',
position: 'absolute',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
>
<defs>
<radialGradient
id={gradientId}
cx="0"
cy="0"
r="1"
gradientTransform="matrix(280 0 0 280 280 280)"
gradientUnits="userSpaceOnUse"
>
<stop />
<stop offset="1" stopOpacity={0} />
</radialGradient>
<clipPath id={clipPathId}>
<path fill="#fff" d="M0 0H560V560H0z" />
</clipPath>
</defs>
<g clipPath={`url(#${clipPathId})`}>
<mask
id={maskId}
style={{ maskType: 'alpha' }}
width="560"
height="560"
x="0"
y="0"
maskUnits="userSpaceOnUse"
>
<path fill={`url(#${gradientId})`} d="M0 0H560V560H0z" />
</mask>
<g stroke="currentColor" strokeDasharray={3} mask={`url(#${maskId})`} opacity={0.4}>
<circle cx="280" cy="280" r="90" />
<circle cx="280" cy="280" r="180" />
<path d="M0 0l560 560M560 0L0 560" />
</g>
</g>
</SvgRoot>
);
}
// ----------------------------------------------------------------------
export function FloatDotIcon({ sx, ...other }: BoxProps<'span'> & MotionProps) {
return (
<Box
component={m.span}
variants={{
initial: { scale: 0 },
animate: { scale: 1, transition },
}}
sx={[
(theme) => ({
...baseStyles(theme),
width: 12,
height: 12,
borderRadius: '50%',
bgcolor: 'currentColor',
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
/>
);
}