feat(backgrounds): add liquid-gradient background

This commit is contained in:
Boris Kovalskii
2026-06-11 10:30:04 +10:00
parent ab91c86f81
commit 71160c6700
8 changed files with 103 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
import { cn } from '@/lib/utils';
import { sanitizeColor, clampNumber, safeSelect } from './types';
import { useAnimationPause } from '@/hooks/useAnimationLoop';
interface Props {
settings: Record<string, unknown>;
}
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
const BLOBS = [
{ anim: 'animate-move-vertical', baseDuration: 30, top: '5%', left: '10%' },
{ anim: 'animate-move-in-circle', baseDuration: 20, top: '35%', left: '50%' },
{ anim: 'animate-move-horizontal', baseDuration: 40, top: '55%', left: '15%' },
{ anim: 'animate-move-in-circle-slow', baseDuration: 40, top: '15%', left: '55%' },
];
const SPEED_MULTIPLIERS: Record<string, number> = {
slow: 1.6,
normal: 1,
fast: 0.5,
};
export default function LiquidGradientBackground({ settings }: Props) {
const paused = useAnimationPause();
const colors = [
sanitizeColor(settings.color1, '#6366f1'),
sanitizeColor(settings.color2, '#ec4899'),
sanitizeColor(settings.color3, '#22d3ee'),
sanitizeColor(settings.color4, '#a855f7'),
];
const speed = safeSelect(settings.speed, ['slow', 'normal', 'fast'] as const, 'normal');
const blurAmount = clampNumber(settings.blurAmount, 10, 120, 60);
const multiplier = SPEED_MULTIPLIERS[speed];
const effectiveBlur = isMobile ? Math.min(blurAmount, 30) : blurAmount;
return (
<div className="absolute inset-0 overflow-hidden">
<div className="absolute inset-0" style={{ filter: `blur(${effectiveBlur}px)` }}>
{BLOBS.map((blob, i) => (
<div
key={i}
className={cn('absolute h-[55%] w-[55%] rounded-full', blob.anim)}
style={{
top: blob.top,
left: blob.left,
opacity: 0.7,
background: `radial-gradient(circle at center, ${colors[i]} 0%, transparent 65%)`,
animationDuration: `${blob.baseDuration * multiplier}s`,
animationPlayState: paused ? 'paused' : 'running',
}}
/>
))}
</div>
</div>
);
}

View File

@@ -22,6 +22,7 @@ const backgroundImports: Record<Exclude<BackgroundType, 'none'>, () => Promise<u
snowfall: () => import('./snowfall'), snowfall: () => import('./snowfall'),
starfield: () => import('./starfield'), starfield: () => import('./starfield'),
'matrix-rain': () => import('./matrix-rain'), 'matrix-rain': () => import('./matrix-rain'),
'liquid-gradient': () => import('./liquid-gradient'),
}; };
/** Prefetch the JS chunk for a background type (call early to avoid lazy-load delay) */ /** Prefetch the JS chunk for a background type (call early to avoid lazy-load delay) */
@@ -55,6 +56,7 @@ export const backgroundComponents: Record<
snowfall: lazy(() => import('./snowfall')), snowfall: lazy(() => import('./snowfall')),
starfield: lazy(() => import('./starfield')), starfield: lazy(() => import('./starfield')),
'matrix-rain': lazy(() => import('./matrix-rain')), 'matrix-rain': lazy(() => import('./matrix-rain')),
'liquid-gradient': lazy(() => import('./liquid-gradient')),
}; };
// Registry of all background definitions with settings for the editor // Registry of all background definitions with settings for the editor
@@ -723,4 +725,36 @@ export const backgroundRegistry: BackgroundDefinition[] = [
}, },
], ],
}, },
{
type: 'liquid-gradient',
labelKey: 'admin.backgrounds.liquidGradient',
descriptionKey: 'admin.backgrounds.liquidGradientDesc',
category: 'css',
settings: [
{ key: 'color1', label: 'admin.backgrounds.color1', type: 'color', default: '#6366f1' },
{ key: 'color2', label: 'admin.backgrounds.color2', type: 'color', default: '#ec4899' },
{ key: 'color3', label: 'admin.backgrounds.color3', type: 'color', default: '#22d3ee' },
{ key: 'color4', label: 'admin.backgrounds.color4', type: 'color', default: '#a855f7' },
{
key: 'speed',
label: 'admin.backgrounds.speed',
type: 'select',
default: 'normal',
options: [
{ label: 'admin.backgrounds.slow', value: 'slow' },
{ label: 'admin.backgrounds.normal', value: 'normal' },
{ label: 'admin.backgrounds.fast', value: 'fast' },
],
},
{
key: 'blurAmount',
label: 'admin.backgrounds.blurAmount',
type: 'number',
min: 10,
max: 120,
step: 5,
default: 60,
},
],
},
]; ];

View File

@@ -18,6 +18,7 @@ export type BackgroundType =
| 'snowfall' | 'snowfall'
| 'starfield' | 'starfield'
| 'matrix-rain' | 'matrix-rain'
| 'liquid-gradient'
| 'none'; | 'none';
export interface AnimationConfig { export interface AnimationConfig {

View File

@@ -1161,6 +1161,8 @@
"starfieldDesc": "Stars flying toward the viewer", "starfieldDesc": "Stars flying toward the viewer",
"matrixRain": "Matrix Rain", "matrixRain": "Matrix Rain",
"matrixRainDesc": "Falling columns of glyphs", "matrixRainDesc": "Falling columns of glyphs",
"liquidGradient": "Liquid Gradient",
"liquidGradientDesc": "Blurred mesh gradient blobs",
"geminiEffect": "Gemini Effect", "geminiEffect": "Gemini Effect",
"geminiEffectDesc": "SVG effect like Google Gemini", "geminiEffectDesc": "SVG effect like Google Gemini",
"speed": "Speed", "speed": "Speed",

View File

@@ -1043,6 +1043,8 @@
"starfieldDesc": "پرواز در میان ستاره‌ها", "starfieldDesc": "پرواز در میان ستاره‌ها",
"matrixRain": "باران ماتریکس", "matrixRain": "باران ماتریکس",
"matrixRainDesc": "ستون‌های نمادهای در حال سقوط", "matrixRainDesc": "ستون‌های نمادهای در حال سقوط",
"liquidGradient": "گرادیان مایع",
"liquidGradientDesc": "حباب‌های گرادیان مات و روان",
"geminiEffect": "افکت Gemini", "geminiEffect": "افکت Gemini",
"geminiEffectDesc": "افکت SVG مشابه Google Gemini", "geminiEffectDesc": "افکت SVG مشابه Google Gemini",
"speed": "سرعت", "speed": "سرعت",

View File

@@ -1191,6 +1191,8 @@
"starfieldDesc": "Полёт сквозь звёздное небо", "starfieldDesc": "Полёт сквозь звёздное небо",
"matrixRain": "Matrix Rain", "matrixRain": "Matrix Rain",
"matrixRainDesc": "Падающие колонки символов", "matrixRainDesc": "Падающие колонки символов",
"liquidGradient": "Liquid Gradient",
"liquidGradientDesc": "Жидкий mesh-градиент из размытых блобов",
"geminiEffect": "Gemini Effect", "geminiEffect": "Gemini Effect",
"geminiEffectDesc": "SVG-эффект как на Google Gemini", "geminiEffectDesc": "SVG-эффект как на Google Gemini",
"speed": "Скорость", "speed": "Скорость",

View File

@@ -1043,6 +1043,8 @@
"starfieldDesc": "向观察者飞来的星星", "starfieldDesc": "向观察者飞来的星星",
"matrixRain": "黑客帝国雨", "matrixRain": "黑客帝国雨",
"matrixRainDesc": "下落的字符列", "matrixRainDesc": "下落的字符列",
"liquidGradient": "流体渐变",
"liquidGradientDesc": "模糊的网格渐变色块",
"geminiEffect": "Gemini效果", "geminiEffect": "Gemini效果",
"geminiEffectDesc": "类似Google Gemini的SVG效果", "geminiEffectDesc": "类似Google Gemini的SVG效果",
"speed": "速度", "speed": "速度",

View File

@@ -23,6 +23,7 @@ const VALID_TYPES: ReadonlySet<string> = new Set<BackgroundType>([
'snowfall', 'snowfall',
'starfield', 'starfield',
'matrix-rain', 'matrix-rain',
'liquid-gradient',
'none', 'none',
]); ]);