mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
fix(backgrounds): make beams and beams-collision colors configurable
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { sanitizeColor } from './types';
|
||||||
import { useAnimationPause } from '@/hooks/useAnimationLoop';
|
import { useAnimationPause } from '@/hooks/useAnimationLoop';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -26,7 +27,11 @@ const BEAMS: BeamOptions[] = [
|
|||||||
{ initialX: 1200, translateX: 1200, duration: 6, repeatDelay: 4, delay: 2, className: 'h-6' },
|
{ initialX: 1200, translateX: 1200, duration: 6, repeatDelay: 4, delay: 2, className: 'h-6' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function Explosion(props: React.HTMLProps<HTMLDivElement>) {
|
function Explosion({
|
||||||
|
beamColor,
|
||||||
|
explosionColor,
|
||||||
|
...props
|
||||||
|
}: React.HTMLProps<HTMLDivElement> & { beamColor: string; explosionColor: string }) {
|
||||||
const spans = Array.from({ length: 20 }, (_, i) => ({
|
const spans = Array.from({ length: 20 }, (_, i) => ({
|
||||||
id: i,
|
id: i,
|
||||||
directionX: Math.floor(Math.random() * 80 - 40),
|
directionX: Math.floor(Math.random() * 80 - 40),
|
||||||
@@ -40,7 +45,10 @@ function Explosion(props: React.HTMLProps<HTMLDivElement>) {
|
|||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 1.5, ease: 'easeOut' }}
|
transition={{ duration: 1.5, ease: 'easeOut' }}
|
||||||
className="absolute -inset-x-10 top-0 m-auto h-2 w-10 rounded-full bg-gradient-to-r from-transparent via-indigo-500 to-transparent blur-sm"
|
className="absolute -inset-x-10 top-0 m-auto h-2 w-10 rounded-full blur-sm"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to right, transparent, ${explosionColor}, transparent)`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{spans.map((span) => (
|
{spans.map((span) => (
|
||||||
<motion.span
|
<motion.span
|
||||||
@@ -48,7 +56,10 @@ function Explosion(props: React.HTMLProps<HTMLDivElement>) {
|
|||||||
initial={{ x: 0, y: 0, opacity: 1 }}
|
initial={{ x: 0, y: 0, opacity: 1 }}
|
||||||
animate={{ x: span.directionX, y: span.directionY, opacity: 0 }}
|
animate={{ x: span.directionX, y: span.directionY, opacity: 0 }}
|
||||||
transition={{ duration: Math.random() * 1.5 + 0.5, ease: 'easeOut' }}
|
transition={{ duration: Math.random() * 1.5 + 0.5, ease: 'easeOut' }}
|
||||||
className="absolute h-1 w-1 rounded-full bg-gradient-to-b from-indigo-500 to-purple-500"
|
className="absolute h-1 w-1 rounded-full"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to bottom, ${beamColor}, ${explosionColor})`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -59,10 +70,14 @@ function CollisionMechanism({
|
|||||||
containerRef,
|
containerRef,
|
||||||
parentRef,
|
parentRef,
|
||||||
beamOptions,
|
beamOptions,
|
||||||
|
beamColor,
|
||||||
|
explosionColor,
|
||||||
}: {
|
}: {
|
||||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
parentRef: React.RefObject<HTMLDivElement | null>;
|
parentRef: React.RefObject<HTMLDivElement | null>;
|
||||||
beamOptions: BeamOptions;
|
beamOptions: BeamOptions;
|
||||||
|
beamColor: string;
|
||||||
|
explosionColor: string;
|
||||||
}) {
|
}) {
|
||||||
const beamRef = useRef<HTMLDivElement>(null);
|
const beamRef = useRef<HTMLDivElement>(null);
|
||||||
const [collision, setCollision] = useState<{
|
const [collision, setCollision] = useState<{
|
||||||
@@ -92,8 +107,6 @@ function CollisionMechanism({
|
|||||||
}
|
}
|
||||||
}, [containerRef, parentRef]);
|
}, [containerRef, parentRef]);
|
||||||
|
|
||||||
// Throttled collision detection loop.
|
|
||||||
// Parent unmounts this component when paused, so no visibility handling needed here.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let animId = 0;
|
let animId = 0;
|
||||||
let lastCheck = 0;
|
let lastCheck = 0;
|
||||||
@@ -114,7 +127,6 @@ function CollisionMechanism({
|
|||||||
};
|
};
|
||||||
}, [checkCollision]);
|
}, [checkCollision]);
|
||||||
|
|
||||||
// Collision reset with proper timeout cleanup
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!collision.detected || !collision.coordinates) return;
|
if (!collision.detected || !collision.coordinates) return;
|
||||||
|
|
||||||
@@ -154,14 +166,19 @@ function CollisionMechanism({
|
|||||||
repeatDelay: beamOptions.repeatDelay,
|
repeatDelay: beamOptions.repeatDelay,
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute left-0 top-20 m-auto h-14 w-px rounded-full bg-gradient-to-t from-indigo-500 via-purple-500 to-transparent',
|
'absolute left-0 top-20 m-auto h-14 w-px rounded-full',
|
||||||
beamOptions.className,
|
beamOptions.className,
|
||||||
)}
|
)}
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to top, ${beamColor}, ${explosionColor}, transparent)`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{collision.detected && collision.coordinates && (
|
{collision.detected && collision.coordinates && (
|
||||||
<Explosion
|
<Explosion
|
||||||
key={`${collision.coordinates.x}-${collision.coordinates.y}`}
|
key={`${collision.coordinates.x}-${collision.coordinates.y}`}
|
||||||
|
beamColor={beamColor}
|
||||||
|
explosionColor={explosionColor}
|
||||||
style={{
|
style={{
|
||||||
left: `${collision.coordinates.x}px`,
|
left: `${collision.coordinates.x}px`,
|
||||||
top: `${collision.coordinates.y}px`,
|
top: `${collision.coordinates.y}px`,
|
||||||
@@ -174,11 +191,14 @@ function CollisionMechanism({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BackgroundBeamsCollision({ settings: _settings }: Props) {
|
export default function BackgroundBeamsCollision({ settings }: Props) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const parentRef = useRef<HTMLDivElement>(null);
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
const paused = useAnimationPause();
|
const paused = useAnimationPause();
|
||||||
|
|
||||||
|
const beamColor = sanitizeColor(settings.beamColor, '#6366f1');
|
||||||
|
const explosionColor = sanitizeColor(settings.explosionColor, '#a855f7');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={parentRef} className="absolute inset-0 overflow-hidden">
|
<div ref={parentRef} className="absolute inset-0 overflow-hidden">
|
||||||
{!paused &&
|
{!paused &&
|
||||||
@@ -188,9 +208,10 @@ export default function BackgroundBeamsCollision({ settings: _settings }: Props)
|
|||||||
beamOptions={beam}
|
beamOptions={beam}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
parentRef={parentRef}
|
parentRef={parentRef}
|
||||||
|
beamColor={beamColor}
|
||||||
|
explosionColor={explosionColor}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{/* Bottom collision line */}
|
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="pointer-events-none absolute inset-x-0 bottom-0 w-full"
|
className="pointer-events-none absolute inset-x-0 bottom-0 w-full"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { sanitizeColor } from './types';
|
||||||
import { useAnimationPause } from '@/hooks/useAnimationLoop';
|
import { useAnimationPause } from '@/hooks/useAnimationLoop';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -99,10 +100,14 @@ function ensureStyles() {
|
|||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(function BackgroundBeams({ settings: _settings }: Props) {
|
export default React.memo(function BackgroundBeams({ settings }: Props) {
|
||||||
const paused = useAnimationPause();
|
const paused = useAnimationPause();
|
||||||
|
|
||||||
// Inject CSS once on first render
|
const gradientStart = sanitizeColor(settings.gradientStart, '#18CCFC');
|
||||||
|
const gradientMid = sanitizeColor(settings.gradientMid, '#6344F5');
|
||||||
|
const gradientEnd = sanitizeColor(settings.gradientEnd, '#AE48FF');
|
||||||
|
const staticColor = sanitizeColor(settings.staticColor, '#d4d4d4');
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
ensureStyles();
|
ensureStyles();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -117,7 +122,6 @@ export default React.memo(function BackgroundBeams({ settings: _settings }: Prop
|
|||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
{/* Static background — all paths at very low opacity */}
|
|
||||||
<path
|
<path
|
||||||
d={bgPath}
|
d={bgPath}
|
||||||
stroke="url(#paint0_radial_beams)"
|
stroke="url(#paint0_radial_beams)"
|
||||||
@@ -125,7 +129,6 @@ export default React.memo(function BackgroundBeams({ settings: _settings }: Prop
|
|||||||
strokeWidth="0.5"
|
strokeWidth="0.5"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Animated beams — only every 3rd path (17 beams), pure stroke-dashoffset */}
|
|
||||||
{animatedPaths.map(({ path, paramIndex }) => (
|
{animatedPaths.map(({ path, paramIndex }) => (
|
||||||
<path
|
<path
|
||||||
key={paramIndex}
|
key={paramIndex}
|
||||||
@@ -144,15 +147,13 @@ export default React.memo(function BackgroundBeams({ settings: _settings }: Prop
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<defs>
|
<defs>
|
||||||
{/* Single shared gradient for all beams (cyan → purple → magenta) */}
|
|
||||||
<linearGradient id="beamGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
<linearGradient id="beamGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
<stop stopColor="#18CCFC" stopOpacity="0" />
|
<stop stopColor={gradientStart} stopOpacity="0" />
|
||||||
<stop offset="10%" stopColor="#18CCFC" />
|
<stop offset="10%" stopColor={gradientStart} />
|
||||||
<stop offset="50%" stopColor="#6344F5" />
|
<stop offset="50%" stopColor={gradientMid} />
|
||||||
<stop offset="100%" stopColor="#AE48FF" stopOpacity="0" />
|
<stop offset="100%" stopColor={gradientEnd} stopOpacity="0" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
|
||||||
{/* Radial gradient for static background */}
|
|
||||||
<radialGradient
|
<radialGradient
|
||||||
id="paint0_radial_beams"
|
id="paint0_radial_beams"
|
||||||
cx="0"
|
cx="0"
|
||||||
@@ -161,9 +162,9 @@ export default React.memo(function BackgroundBeams({ settings: _settings }: Prop
|
|||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
gradientTransform="translate(352 34) rotate(90) scale(555 1560.62)"
|
gradientTransform="translate(352 34) rotate(90) scale(555 1560.62)"
|
||||||
>
|
>
|
||||||
<stop offset="0.0666667" stopColor="#d4d4d4" />
|
<stop offset="0.0666667" stopColor={staticColor} />
|
||||||
<stop offset="0.243243" stopColor="#d4d4d4" />
|
<stop offset="0.243243" stopColor={staticColor} />
|
||||||
<stop offset="0.43594" stopColor="white" stopOpacity="0" />
|
<stop offset="0.43594" stopColor={staticColor} stopOpacity="0" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -234,14 +234,37 @@ export const backgroundRegistry: BackgroundDefinition[] = [
|
|||||||
labelKey: 'admin.backgrounds.beams',
|
labelKey: 'admin.backgrounds.beams',
|
||||||
descriptionKey: 'admin.backgrounds.beamsDesc',
|
descriptionKey: 'admin.backgrounds.beamsDesc',
|
||||||
category: 'svg',
|
category: 'svg',
|
||||||
settings: [],
|
settings: [
|
||||||
|
{
|
||||||
|
key: 'gradientStart',
|
||||||
|
label: 'admin.backgrounds.color1',
|
||||||
|
type: 'color',
|
||||||
|
default: '#18CCFC',
|
||||||
|
},
|
||||||
|
{ key: 'gradientMid', label: 'admin.backgrounds.color2', type: 'color', default: '#6344F5' },
|
||||||
|
{ key: 'gradientEnd', label: 'admin.backgrounds.color3', type: 'color', default: '#AE48FF' },
|
||||||
|
{
|
||||||
|
key: 'staticColor',
|
||||||
|
label: 'admin.backgrounds.fillColor',
|
||||||
|
type: 'color',
|
||||||
|
default: '#d4d4d4',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'background-beams-collision',
|
type: 'background-beams-collision',
|
||||||
labelKey: 'admin.backgrounds.beamsCollision',
|
labelKey: 'admin.backgrounds.beamsCollision',
|
||||||
descriptionKey: 'admin.backgrounds.beamsCollisionDesc',
|
descriptionKey: 'admin.backgrounds.beamsCollisionDesc',
|
||||||
category: 'svg',
|
category: 'svg',
|
||||||
settings: [],
|
settings: [
|
||||||
|
{ key: 'beamColor', label: 'admin.backgrounds.beamColor', type: 'color', default: '#6366f1' },
|
||||||
|
{
|
||||||
|
key: 'explosionColor',
|
||||||
|
label: 'admin.backgrounds.explosionColor',
|
||||||
|
type: 'color',
|
||||||
|
default: '#a855f7',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'gradient-animation',
|
type: 'gradient-animation',
|
||||||
|
|||||||
@@ -1170,6 +1170,8 @@
|
|||||||
"trailColor": "Trail Color",
|
"trailColor": "Trail Color",
|
||||||
"bgStarColor": "Background Stars Color",
|
"bgStarColor": "Background Stars Color",
|
||||||
"pointerColor": "Pointer Color",
|
"pointerColor": "Pointer Color",
|
||||||
|
"beamColor": "Beam Color",
|
||||||
|
"explosionColor": "Explosion Color",
|
||||||
"bgColor": "Background Color",
|
"bgColor": "Background Color",
|
||||||
"fillColor": "Fill Color",
|
"fillColor": "Fill Color",
|
||||||
"color1": "Color 1",
|
"color1": "Color 1",
|
||||||
|
|||||||
@@ -1200,6 +1200,8 @@
|
|||||||
"trailColor": "Цвет следа",
|
"trailColor": "Цвет следа",
|
||||||
"bgStarColor": "Цвет фоновых звёзд",
|
"bgStarColor": "Цвет фоновых звёзд",
|
||||||
"pointerColor": "Цвет курсорного блоба",
|
"pointerColor": "Цвет курсорного блоба",
|
||||||
|
"beamColor": "Цвет лучей",
|
||||||
|
"explosionColor": "Цвет взрыва",
|
||||||
"bgColor": "Цвет фона",
|
"bgColor": "Цвет фона",
|
||||||
"fillColor": "Цвет заливки",
|
"fillColor": "Цвет заливки",
|
||||||
"color1": "Цвет 1",
|
"color1": "Цвет 1",
|
||||||
|
|||||||
Reference in New Issue
Block a user