feat: add brand logo and animated background
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,145 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
interface AnimatedBackgroundProps {
|
||||
/** Intensity multiplier (0.5 = subtle, 1 = default, 1.5 = bold) */
|
||||
intensity?: number;
|
||||
}
|
||||
|
||||
export default function AnimatedBackground({ intensity = 1 }: AnimatedBackgroundProps) {
|
||||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
const base = isDark ? 0.25 : 0.4;
|
||||
|
||||
return (
|
||||
<Box
|
||||
aria-hidden
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
zIndex: 0,
|
||||
pointerEvents: 'none',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Orb
|
||||
color={isDark ? 'rgba(255,107,0,0.6)' : 'rgba(255,107,0,0.55)'}
|
||||
size={620}
|
||||
opacity={base * intensity}
|
||||
delay={0}
|
||||
duration={28}
|
||||
x={['-15%', '5%', '-10%']}
|
||||
y={['-10%', '15%', '0%']}
|
||||
scale={[1, 1.15, 1]}
|
||||
/>
|
||||
<Orb
|
||||
color={isDark ? 'rgba(45,90,39,0.55)' : 'rgba(45,90,39,0.45)'}
|
||||
size={520}
|
||||
opacity={base * intensity * 0.9}
|
||||
delay={4}
|
||||
duration={34}
|
||||
x={['70%', '50%', '75%']}
|
||||
y={['10%', '30%', '5%']}
|
||||
scale={[1, 1.1, 1]}
|
||||
/>
|
||||
<Orb
|
||||
color={isDark ? 'rgba(59,130,246,0.35)' : 'rgba(59,130,246,0.3)'}
|
||||
size={480}
|
||||
opacity={base * intensity * 0.7}
|
||||
delay={8}
|
||||
duration={40}
|
||||
x={['20%', '40%', '15%']}
|
||||
y={['65%', '50%', '70%']}
|
||||
scale={[1, 1.08, 1]}
|
||||
/>
|
||||
<Orb
|
||||
color={isDark ? 'rgba(245,158,11,0.3)' : 'rgba(245,158,11,0.28)'}
|
||||
size={420}
|
||||
opacity={base * intensity * 0.6}
|
||||
delay={2}
|
||||
duration={36}
|
||||
x={['60%', '80%', '55%']}
|
||||
y={['70%', '55%', '80%']}
|
||||
scale={[1, 1.12, 1]}
|
||||
/>
|
||||
|
||||
{/* Subtle grid pattern */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
opacity: isDark ? 0.18 : 0.35,
|
||||
backgroundImage:
|
||||
isDark
|
||||
? 'radial-gradient(circle at 1px 1px, rgba(255,255,255,0.05) 1px, transparent 0)'
|
||||
: 'radial-gradient(circle at 1px 1px, rgba(15,23,42,0.06) 1px, transparent 0)',
|
||||
backgroundSize: '28px 28px',
|
||||
maskImage:
|
||||
'linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.85) 25%, rgba(0,0,0,0.85) 75%, transparent 100%)',
|
||||
WebkitMaskImage:
|
||||
'linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.85) 25%, rgba(0,0,0,0.85) 75%, transparent 100%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Vignette so foreground content reads clearly */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background: isDark
|
||||
? 'radial-gradient(circle at 50% 30%, transparent 0%, rgba(11,15,26,0.4) 80%)'
|
||||
: 'radial-gradient(circle at 50% 30%, transparent 0%, rgba(249,250,251,0.45) 80%)',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
interface OrbProps {
|
||||
color: string;
|
||||
size: number;
|
||||
opacity: number;
|
||||
delay: number;
|
||||
duration: number;
|
||||
x: string[];
|
||||
y: string[];
|
||||
scale: number[];
|
||||
}
|
||||
|
||||
function Orb({ color, size, opacity, delay, duration, x, y, scale }: OrbProps) {
|
||||
// Build keyframes inline as a CSS custom animation
|
||||
const animName = `pp-orb-${Math.abs(
|
||||
delay * 31 + duration * 7 + size
|
||||
)}`;
|
||||
|
||||
const keyframes = `
|
||||
@keyframes ${animName} {
|
||||
0% { transform: translate3d(${x[0]}, ${y[0]}, 0) scale(${scale[0]}); }
|
||||
50% { transform: translate3d(${x[1]}, ${y[1]}, 0) scale(${scale[1]}); }
|
||||
100% { transform: translate3d(${x[2]}, ${y[2]}, 0) scale(${scale[2]}); }
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{keyframes}</style>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: size,
|
||||
height: size,
|
||||
left: 0,
|
||||
top: 0,
|
||||
borderRadius: '50%',
|
||||
background: `radial-gradient(circle, ${color} 0%, transparent 65%)`,
|
||||
filter: 'blur(80px)',
|
||||
opacity,
|
||||
willChange: 'transform',
|
||||
animation: `${animName} ${duration}s ease-in-out ${delay}s infinite alternate`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
interface LogoProps {
|
||||
/** Total box size in pixels. */
|
||||
size?: number;
|
||||
/** Show the rounded white container backplate (for use over coloured surfaces). */
|
||||
withBackplate?: boolean;
|
||||
/** Padding inside the backplate (if any). */
|
||||
padding?: number;
|
||||
/** Optional border-radius override (defaults to 2 = 8px). */
|
||||
rounded?: number;
|
||||
/** className for parent positioning. */
|
||||
className?: string;
|
||||
/** Click handler. */
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default function Logo({
|
||||
size = 36,
|
||||
withBackplate = false,
|
||||
padding,
|
||||
rounded,
|
||||
className,
|
||||
onClick,
|
||||
}: LogoProps) {
|
||||
const inner = (
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="PatchPilot Logo"
|
||||
width={size}
|
||||
height={size}
|
||||
priority
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
display: 'block',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!withBackplate) {
|
||||
return (
|
||||
<Box
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
width: size,
|
||||
height: size,
|
||||
flexShrink: 0,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: onClick ? 'pointer' : undefined,
|
||||
}}
|
||||
>
|
||||
{inner}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
width: size,
|
||||
height: size,
|
||||
flexShrink: 0,
|
||||
p: padding ?? 0.5,
|
||||
borderRadius: rounded ?? 2,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: 'background.paper',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
boxShadow: 'var(--pp-shadow-soft)',
|
||||
cursor: onClick ? 'pointer' : undefined,
|
||||
}}
|
||||
>
|
||||
{inner}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user