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