a487a8c293
- Implemented AdjustAIButton for AI-driven plan adjustments with a confetti effect on success. - Created EditPlanButton to allow editing of plan details with form validation and optimistic updates. - Added AsignaturaPreviewCard to display course previews with relevant statistics and details. - Introduced Field component for consistent form field labeling. - Developed GradientMesh for dynamic background effects based on color input. - Added Pulse component for skeleton loading states. - Created SmallStat and StatCard components for displaying statistical information in a card format. - Implemented utility functions in planHelpers for color manipulation and formatting. - Established planQueries for fetching plan and course data from the database. - Updated the plan detail route to utilize new components and queries for improved user experience.
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
import { useEffect, useRef } from "react"
|
|
import gsap from "gsap"
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger"
|
|
gsap.registerPlugin(ScrollTrigger)
|
|
import { hexToRgb, lighten, toRGBA } from "./planHelpers"
|
|
|
|
export function GradientMesh({ color }: { color?: string | null }) {
|
|
const meshRef = useRef<HTMLDivElement>(null)
|
|
const base = hexToRgb(color)
|
|
const soft = lighten(base, 20)
|
|
const pop = lighten(base, -20)
|
|
|
|
useEffect(() => {
|
|
if (!meshRef.current) return
|
|
const blobs = meshRef.current.querySelectorAll('.blob')
|
|
blobs.forEach((el, i) => {
|
|
gsap.to(el, {
|
|
x: gsap.utils.random(-30, 30),
|
|
y: gsap.utils.random(-20, 20),
|
|
rotate: gsap.utils.random(-6, 6),
|
|
duration: gsap.utils.random(6, 10),
|
|
ease: 'sine.inOut',
|
|
yoyo: true,
|
|
repeat: -1,
|
|
delay: i * 0.2,
|
|
})
|
|
})
|
|
return () => gsap.killTweensOf(blobs)
|
|
}, [color])
|
|
|
|
return (
|
|
<div ref={meshRef} className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
|
|
<div className="blob absolute -top-24 -left-24 w-[38rem] h-[38rem] rounded-full blur-3xl"
|
|
style={{ background: `radial-gradient(circle, ${toRGBA(soft, .35)}, transparent 60%)` }} />
|
|
<div className="blob absolute -bottom-28 -right-20 w-[34rem] h-[34rem] rounded-full blur-[60px]"
|
|
style={{ background: `radial-gradient(circle, ${toRGBA(base, .28)}, transparent 60%)` }} />
|
|
<div className="blob absolute top-1/3 left-1/2 -translate-x-1/2 w-[22rem] h-[22rem] rounded-full blur-[50px]"
|
|
style={{ background: `radial-gradient(circle, ${toRGBA(pop, .22)}, transparent 60%)` }} />
|
|
<div className="absolute inset-0 opacity-40"
|
|
style={{ background: 'radial-gradient(800px 300px at 20% -10%, #fff, transparent 60%)' }} />
|
|
</div>
|
|
)
|
|
}
|